11package com .objectcomputing .checkins .security ;
22
3+ import com .nimbusds .jwt .JWT ;
4+ import com .nimbusds .jwt .JWTClaimsSet ;
5+ import com .nimbusds .jwt .JWTParser ;
36import com .objectcomputing .checkins .Environments ;
47import com .objectcomputing .checkins .services .memberprofile .MemberProfile ;
58import com .objectcomputing .checkins .services .memberprofile .MemberProfileServices ;
1417import io .micronaut .http .HttpResponse ;
1518import io .micronaut .http .MediaType ;
1619import 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 .*;
2221import io .micronaut .http .cookie .Cookie ;
2322import io .micronaut .http .cookie .SameSite ;
2423import io .micronaut .http .netty .cookies .NettyCookie ;
2928import io .micronaut .security .event .LoginSuccessfulEvent ;
3029import io .micronaut .security .handlers .LoginHandler ;
3130import io .micronaut .security .rules .SecurityRule ;
31+ import io .micronaut .security .token .jwt .generator .JwtTokenGenerator ;
32+ import io .micronaut .security .token .jwt .validator .ReactiveJsonWebTokenValidator ;
3233import org .slf4j .Logger ;
3334import org .slf4j .LoggerFactory ;
3435
3536import 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 ;
37+ import java .text .ParseException ;
38+ import java .util .*;
4239import java .util .stream .Collectors ;
4340
4441@ Requires (env = {Environments .LOCAL , Environment .DEVELOPMENT })
@@ -54,67 +51,90 @@ public class ImpersonationController {
5451 private final MemberProfileServices memberProfileServices ;
5552 private final RoleServices roleServices ;
5653 private final RolePermissionServices rolePermissionServices ;
54+ private final JwtTokenGenerator generator ;
5755
5856 /**
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
57+ * @param loginHandler A collaborator which helps to build HTTP response depending on success or failure.
58+ * @param eventPublisher The application event publisher
59+ * @param roleServices Role services
60+ * @param rolePermissionServices Role permission services
61+ * @param memberProfileServices Member profile services
62+ * @param generator Generator for creating and signing the new token
6463 */
6564 public ImpersonationController (LoginHandler loginHandler ,
6665 ApplicationEventPublisher eventPublisher ,
6766 RoleServices roleServices ,
6867 RolePermissionServices rolePermissionServices ,
69- MemberProfileServices memberProfileServices ) {
68+ MemberProfileServices memberProfileServices ,
69+ JwtTokenGenerator generator ) {
7070 this .loginHandler = loginHandler ;
7171 this .eventPublisher = eventPublisher ;
7272 this .roleServices = roleServices ;
7373 this .rolePermissionServices = rolePermissionServices ;
7474 this .memberProfileServices = memberProfileServices ;
75+ this .generator = generator ;
7576 }
7677
7778 @ Consumes ({MediaType .APPLICATION_FORM_URLENCODED , MediaType .APPLICATION_JSON })
7879 @ Post ("/begin" )
7980 @ RequiredPermission (Permission .CAN_IMPERSONATE_MEMBERS )
8081 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" );
82+ final Cookie jwt = request .getCookies ().get (JWT );
83+ if (jwt == null ) {
84+ // The user is required to be logged in. If this is null,
85+ // we are in an impossible state!
86+ LOG .error ("Unable to locate the JWT" );
8687 return HttpResponse .unauthorized ();
87- } else {
88- LOG .info ("Processing request to switch to user \ ' {}\ ' " , email );
88+ } else {
89+ LOG .info ("Processing request to switch to user '{}'" , email );
8990 Set <MemberProfile > memberProfiles = memberProfileServices .findByValues (null , null , null , null , email , null , Boolean .FALSE );
9091 Iterator <MemberProfile > iterator = memberProfiles .iterator ();
91- if (!iterator .hasNext ()) return HttpResponse .badRequest ();
92+ if (!iterator .hasNext ()) return HttpResponse .badRequest ();
9293
9394 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 () : "" ;
95+ LOG .info ("Profile exists for '{}'" , email );
96+ String firstName = memberProfile .getFirstName () != null ? memberProfile .getFirstName () : "" ;
97+ String lastName = memberProfile .getLastName () != null ? memberProfile .getLastName () : "" ;
9798 Set <String > roles = roleServices .findUserRoles (memberProfile .getId ()).stream ().map (role -> role .getRole ()).collect (Collectors .toSet ());
9899 Set <String > permissions = rolePermissionServices .findUserPermissions (memberProfile .getId ()).stream ().map (permission -> permission .name ()).collect (Collectors .toSet ());
99100
100101 Map <String , Object > newAttributes = new HashMap <>();
101- newAttributes .put ("email" , memberProfile .getWorkEmail ());
102- newAttributes .put ("name" , firstName + ' ' + lastName );
103- newAttributes .put ("picture" , "" );
102+ newAttributes .put ("email" , memberProfile .getWorkEmail ());
103+ newAttributes .put ("name" , firstName + ' ' + lastName );
104+ newAttributes .put ("picture" , "" );
104105 newAttributes .put ("roles" , roles );
105106 newAttributes .put ("permissions" , permissions );
106- newAttributes .put ("openIdToken" , "" );
107+ JWTClaimsSet newSet = null ;
108+ try {
109+ JWT parse = JWTParser .parse (jwt .getValue ());
110+ JWTClaimsSet jwtClaimsSet = parse .getJWTClaimsSet ();
111+ Map <String , Object > claims = new HashMap <>();
112+ claims .put ("email" , memberProfile .getWorkEmail ());
113+ claims .put ("name" , firstName + ' ' + lastName );
114+ claims .put ("picture" , "" );
115+ claims .put ("exp" , ((Date ) jwtClaimsSet .getClaims ().get ("exp" )).getTime ());
116+ claims .put ("iss" , jwtClaimsSet .getClaims ().get ("iss" ));
117+ claims .put ("aud" , jwtClaimsSet .getClaims ().get ("aud" ));
118+ claims .put ("sub" , jwtClaimsSet .getClaims ().get ("sub" ));
119+ newSet = JWTClaimsSet .parse (claims );
120+ Optional <String > signed = generator .generateToken (claims );
121+
122+ String token = signed .get ();
123+ if (newSet != null ) newAttributes .put ("openIdToken" , token );
124+ } catch (ParseException e ) {
125+ throw new RuntimeException (e );
126+ }
107127
108128 LOG .info ("Building authentication" );
109129 Authentication updatedAuth = Authentication .build (email , roles , newAttributes );
110130 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.
131+ eventPublisher .publishEvent (new LoginSuccessfulEvent (updatedAuth , null , Locale .getDefault ()));
132+ // Store the old JWT to allow the user to revert the impersonation.
113133 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- }
134+ return ((MutableHttpResponse ) loginHandler .loginSuccess (updatedAuth , request )).cookie (
135+ new NettyCookie (originalJWT , jwt .getValue ()).path ("/" ).sameSite (SameSite .Strict )
136+ .maxAge (jwt .getMaxAge ()));
137+ }
118138 }
119139
120140 @ Produces (MediaType .TEXT_HTML )
@@ -127,13 +147,13 @@ public HttpResponse<Object> revert(HttpRequest<?> request) {
127147 // Swap the OJWT back to the JWT and remove the original JWT
128148 Set <Cookie > cookies = new HashSet <Cookie >();
129149 cookies .add (new NettyCookie (JWT , ojwt .getValue ()).path ("/" )
130- .sameSite (SameSite .Strict )
131- .maxAge (ojwt .getMaxAge ()).httpOnly ());
150+ .sameSite (SameSite .Strict )
151+ .maxAge (ojwt .getMaxAge ()).httpOnly ());
132152 cookies .add (new NettyCookie (originalJWT , "" ).path ("/" ).maxAge (0 ));
133153
134154 // Redirect to "/" while setting the cookies.
135155 return HttpResponse .temporaryRedirect (URI .create ("/" ))
136- .cookies (cookies );
156+ .cookies (cookies );
137157 }
138158 }
139159}
0 commit comments