Skip to content

Commit

Permalink
Try a slimmed down impersonation controller impl
Browse files Browse the repository at this point in the history
  • Loading branch information
mkimberlin committed Aug 14, 2024
1 parent 3827323 commit 3facb69
Showing 1 changed file with 46 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,37 @@

import com.objectcomputing.checkins.Environments;
import com.objectcomputing.checkins.services.memberprofile.MemberProfile;
import com.objectcomputing.checkins.services.memberprofile.currentuser.CurrentUserServices;
import com.objectcomputing.checkins.services.memberprofile.MemberProfileServices;
import com.objectcomputing.checkins.services.permissions.Permission;
import com.objectcomputing.checkins.services.permissions.RequiredPermission;
import io.micronaut.context.env.Environment;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.cookie.SameSite;
import io.micronaut.http.cookie.Cookie;
import io.micronaut.http.netty.cookies.NettyCookie;
import io.micronaut.security.utils.SecurityService;
import com.objectcomputing.checkins.services.role.RoleServices;
import com.objectcomputing.checkins.services.role.role_permissions.RolePermissionServices;
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.env.Environment;
import io.micronaut.context.event.ApplicationEventPublisher;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Consumes;
import io.micronaut.http.annotation.Produces;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.annotation.*;
import io.micronaut.http.cookie.Cookie;
import io.micronaut.http.cookie.SameSite;
import io.micronaut.http.netty.cookies.NettyCookie;
import io.micronaut.scheduling.TaskExecutors;
import io.micronaut.scheduling.annotation.ExecuteOn;
import io.micronaut.security.annotation.Secured;
import io.micronaut.security.authentication.Authentication;
import io.micronaut.security.authentication.AuthenticationResponse;
import io.micronaut.security.authentication.Authenticator;
import io.micronaut.security.authentication.UsernamePasswordCredentials;
import io.micronaut.security.event.LoginFailedEvent;
import io.micronaut.security.event.LoginSuccessfulEvent;
import io.micronaut.security.handlers.LoginHandler;
import io.micronaut.security.rules.SecurityRule;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.HashSet;
import java.util.Set;
import java.net.URI;

import io.micronaut.security.utils.SecurityService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.util.*;
import java.util.stream.Collectors;

@Requires(env = {Environments.LOCAL, Environment.DEVELOPMENT})
@Controller("/impersonation")
@ExecuteOn(TaskExecutors.BLOCKING)
Expand All @@ -54,85 +41,79 @@ public class ImpersonationController {
public static final String JWT = "JWT";
public static final String originalJWT = "OJWT";
private static final Logger LOG = LoggerFactory.getLogger(ImpersonationController.class);
protected final Authenticator authenticator;
protected final LoginHandler loginHandler;
protected final ApplicationEventPublisher eventPublisher;
private final CurrentUserServices currentUserServices;
private final SecurityService securityService;
private final MemberProfileServices memberProfileServices;
private final RoleServices roleServices;
private final RolePermissionServices rolePermissionServices;

/**
* @param authenticator {@link Authenticator} collaborator
* @param loginHandler A collaborator which helps to build HTTP response depending on success or failure.
* @param eventPublisher The application event publisher
* @param currentUserServices Current User services
* @param securityService The Security Service
* @param roleServices Role services
* @param rolePermissionServices Role permission services
* @param memberProfileServices Member profile services
*/
public ImpersonationController(Authenticator authenticator,
LoginHandler loginHandler,
public ImpersonationController(LoginHandler loginHandler,
ApplicationEventPublisher eventPublisher,
CurrentUserServices currentUserServices,
SecurityService securityService) {
this.authenticator = authenticator;
RoleServices roleServices,
RolePermissionServices rolePermissionServices,
MemberProfileServices memberProfileServices) {
this.loginHandler = loginHandler;
this.eventPublisher = eventPublisher;
this.currentUserServices = currentUserServices;
this.securityService = securityService;
this.roleServices = roleServices;
this.rolePermissionServices = rolePermissionServices;
this.memberProfileServices = memberProfileServices;
}

@Consumes({MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON})
@Post("/begin")
@RequiredPermission(Permission.CAN_IMPERSONATE_MEMBERS)
public Mono<Object> auth(HttpRequest<?> request, String email) {
if (securityService != null) {
Optional<Authentication> auth = securityService.getAuthentication();
if (auth.isPresent() && auth.get().getAttributes().get("email") != null) {
public HttpResponse<Void> auth(HttpRequest<?> request, String email) {
final Cookie jwt = request.getCookies().get(JWT);
if (jwt == null) {
// The user is required to be logged in. If this is null,
// we are in an impossible state!
LOG.error("Unable to locate the JWT");
return HttpResponse.unauthorized();
} else {
UsernamePasswordCredentials usernamePasswordCredentials = new UsernamePasswordCredentials(email, "");
Flux<AuthenticationResponse> authenticationResponseFlux =
Flux.from(authenticator.authenticate(request, usernamePasswordCredentials));
return authenticationResponseFlux.map(authenticationResponse -> {
if (authenticationResponse.isAuthenticated() && authenticationResponse.getAuthentication().isPresent()) {
Authentication authentication = authenticationResponse.getAuthentication().get();
// Get member profile by work email
MemberProfile memberProfile = currentUserServices.findOrSaveUser("", "", email);
LOG.info("Processing request to switch to user \'{}\'", email);
Set<MemberProfile> memberProfiles = memberProfileServices.findByValues(null, null, null, null, email, null, Boolean.FALSE);
Iterator<MemberProfile> iterator = memberProfiles.iterator();
if(!iterator.hasNext()) return HttpResponse.badRequest();

MemberProfile memberProfile = iterator.next();
LOG.info("Profile exists for \'{}\'", email);
String firstName = memberProfile.getFirstName() != null ? memberProfile.getFirstName() : "";
String lastName = memberProfile.getLastName() != null ? memberProfile.getLastName() : "";
Set<String> roles = roleServices.findUserRoles(memberProfile.getId()).stream().map(role -> role.getRole()).collect(Collectors.toSet());
Set<String> permissions = rolePermissionServices.findUserPermissions(memberProfile.getId()).stream().map(permission -> permission.name()).collect(Collectors.toSet());

Map<String, Object> newAttributes = new HashMap<>(authentication.getAttributes());
Map<String, Object> newAttributes = new HashMap<>();
newAttributes.put("email", memberProfile.getWorkEmail());
newAttributes.put("name", firstName + ' ' + lastName);
newAttributes.put("picture", "");
Authentication updatedAuth = Authentication.build(authentication.getName(), authentication.getRoles(), newAttributes);
newAttributes.put("roles", roles);
newAttributes.put("permissions", permissions);

LOG.info("Building authentication");
Authentication updatedAuth = Authentication.build(email, roles, newAttributes);
LOG.info("Publishing login");
eventPublisher.publishEvent(new LoginSuccessfulEvent(updatedAuth, null, Locale.getDefault()));
// Store the old JWT to allow the user to revert the impersonation.
LOG.info("Attempting to swap tokens");
return ((MutableHttpResponse)loginHandler.loginSuccess(updatedAuth, request)).cookie(
new NettyCookie(originalJWT, jwt.getValue()).path("/").sameSite(SameSite.Strict)
.maxAge(jwt.getMaxAge()));
} else {
eventPublisher.publishEvent(new LoginFailedEvent(authenticationResponse, null, null, Locale.getDefault()));
return loginHandler.loginFailed(authenticationResponse, request);
}
}).single(Mono.just(HttpResponse.unauthorized()));
}
} else {
LOG.error("Attempted impersonation without authentication.");
}
}
return Mono.just(HttpResponse.unauthorized());
}

@Produces(MediaType.TEXT_HTML)
@Get("/end")
public HttpResponse<Object> revert(HttpRequest<?> request) {
final Cookie ojwt = request.getCookies().get(originalJWT);
if (ojwt == null) {
return HttpResponse.unauthorized();
return HttpResponse.badRequest();
} else {
// Swap the OJWT back to the JWT and remove the original JWT
Set<Cookie> cookies = new HashSet<Cookie>();
Expand Down

0 comments on commit 3facb69

Please sign in to comment.