Skip to content

Commit 420da68

Browse files
committed
Fixed impersonation
1 parent 646c35b commit 420da68

File tree

3 files changed

+88
-41
lines changed

3 files changed

+88
-41
lines changed
Lines changed: 61 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.objectcomputing.checkins.security;
22

3+
import com.nimbusds.jwt.JWT;
4+
import com.nimbusds.jwt.JWTClaimsSet;
5+
import com.nimbusds.jwt.JWTParser;
36
import com.objectcomputing.checkins.Environments;
47
import com.objectcomputing.checkins.services.memberprofile.MemberProfile;
58
import com.objectcomputing.checkins.services.memberprofile.MemberProfileServices;
@@ -14,11 +17,7 @@
1417
import io.micronaut.http.HttpResponse;
1518
import io.micronaut.http.MediaType;
1619
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.*;
2221
import io.micronaut.http.cookie.Cookie;
2322
import io.micronaut.http.cookie.SameSite;
2423
import io.micronaut.http.netty.cookies.NettyCookie;
@@ -29,16 +28,14 @@
2928
import io.micronaut.security.event.LoginSuccessfulEvent;
3029
import io.micronaut.security.handlers.LoginHandler;
3130
import io.micronaut.security.rules.SecurityRule;
31+
import io.micronaut.security.token.jwt.generator.JwtTokenGenerator;
32+
import io.micronaut.security.token.jwt.validator.ReactiveJsonWebTokenValidator;
3233
import org.slf4j.Logger;
3334
import org.slf4j.LoggerFactory;
3435

3536
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;
37+
import java.text.ParseException;
38+
import java.util.*;
4239
import 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
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
micronaut:
2+
server:
3+
cors:
4+
enabled: true
5+
configurations:
6+
web:
7+
allowedOriginsRegex:
8+
- ^http(|s):\/\/localhost:.*$
9+
---
10+
datasources:
11+
default:
12+
url: ${JDBC_URL:`jdbc:postgresql://localhost:5432/checkinsdb`}
13+
username: postgres
14+
password: "postgres"
15+
---
16+
flyway:
17+
enabled: enabled
18+
datasources:
19+
default:
20+
locations:
21+
- "classpath:db/common"
22+
- "classpath:db/dev"

server/src/main/resources/application.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ micronaut:
4848
secret: ${ OAUTH_REFRESH_TOKEN_SECRET:'pleaseChangeThisSecretForANewOne' }
4949
access-token:
5050
expiration: 28800
51+
signatures:
52+
secret:
53+
generator:
54+
jws-algorithm: HS256
55+
secret: ${ JWT_GENERATOR_SIGNATURE_SECRET:'pleaseChangeThisSecretForANewOne' }
5156
oauth2:
5257
callback-uri: ${ OAUTH_CALLBACK_URI }
5358
clients:

0 commit comments

Comments
 (0)