Skip to content

Commit

Permalink
Merge branch 'develop' into feature-2532/graal
Browse files Browse the repository at this point in the history
  • Loading branch information
mkimberlin committed Aug 15, 2024
2 parents 98077d1 + e3ecead commit efac159
Show file tree
Hide file tree
Showing 70 changed files with 2,794 additions and 6,371 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ node_modules
.run
**/run.bat
*.sh
*.swp
4 changes: 2 additions & 2 deletions docs/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,8 @@ GEM
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
rexml (3.2.8)
strscan (>= 3.0.9)
rexml (3.3.3)
strscan
rouge (3.30.0)
rubyzip (2.3.2)
safe_yaml (1.0.5)
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started/running/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Then run the application:

This step requires your environment to be set. Reach out to the team for the `run.sh` file which, in addition to setting your environment, will execute the `:server:run` gradle task.

You can access the application by navigating to [http://localhost:8080](http://localhost:8080) in your browser.
You can access the application by navigating to [http://localhost:8080/login](http://localhost:8080/login) in your browser.

Next login to the application with:

Expand Down
2 changes: 0 additions & 2 deletions docs/getting-started/setup/windowsos.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ grand_parent: Getting Started
- [Install Git](#install-git)
- [Install Hub](#install-hub)
- [SSH Keys](#ssh-keys)
- [Install jEnv](#install-jenv)
- [Plugins](#plugins)
- [Install OpenJDK](#install-openjdk)
- [Install NVM](#install-nvm)
- [Install Yarn](#install-yarn)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,47 @@ public static class ApplicationConfig {

@NotBlank
private String name;

@NotNull
private FeedbackConfig feedback;

@NotNull
private GoogleApiConfig googleApi;

@Getter
@Setter
@ConfigurationProperties("feedback")
public static class FeedbackConfig {

@NotNull
private Integer maxSuggestions;

@NotBlank
private String requestSubject;
}

@Getter
@Setter
@ConfigurationProperties("google-api")
public static class GoogleApiConfig {

@NotBlank
private String delegatedUser;

@NotNull
private ScopeConfig scopes;

@Getter
@Setter
@ConfigurationProperties("scopes")
public static class ScopeConfig {

@NotBlank
private String scopeForDriveApi;

@NotBlank
private String scopeForDirectoryApi;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package com.objectcomputing.checkins.security;

import com.objectcomputing.checkins.Environments;
import com.objectcomputing.checkins.services.memberprofile.MemberProfile;
import com.objectcomputing.checkins.services.memberprofile.MemberProfileServices;
import com.objectcomputing.checkins.services.permissions.Permission;
import com.objectcomputing.checkins.services.permissions.RequiredPermission;
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.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.event.LoginSuccessfulEvent;
import io.micronaut.security.handlers.LoginHandler;
import io.micronaut.security.rules.SecurityRule;
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)
@Secured(SecurityRule.IS_AUTHENTICATED)
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 LoginHandler loginHandler;
protected final ApplicationEventPublisher eventPublisher;
private final MemberProfileServices memberProfileServices;
private final RoleServices roleServices;
private final RolePermissionServices rolePermissionServices;

/**
* @param loginHandler A collaborator which helps to build HTTP response depending on success or failure.
* @param eventPublisher The application event publisher
* @param roleServices Role services
* @param rolePermissionServices Role permission services
* @param memberProfileServices Member profile services
*/
public ImpersonationController(LoginHandler loginHandler,
ApplicationEventPublisher eventPublisher,
RoleServices roleServices,
RolePermissionServices rolePermissionServices,
MemberProfileServices memberProfileServices) {
this.loginHandler = loginHandler;
this.eventPublisher = eventPublisher;
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 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 {
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<>();
newAttributes.put("email", memberProfile.getWorkEmail());
newAttributes.put("name", firstName + ' ' + lastName);
newAttributes.put("picture", "");
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()));
}
}

@Produces(MediaType.TEXT_HTML)
@Get("/end")
public HttpResponse<Object> revert(HttpRequest<?> request) {
final Cookie ojwt = request.getCookies().get(originalJWT);
if (ojwt == null) {
return HttpResponse.badRequest();
} else {
// Swap the OJWT back to the JWT and remove the original JWT
Set<Cookie> cookies = new HashSet<Cookie>();
cookies.add(new NettyCookie(JWT, ojwt.getValue()).path("/")
.sameSite(SameSite.Strict)
.maxAge(ojwt.getMaxAge()).httpOnly());
cookies.add(new NettyCookie(originalJWT, "").path("/").maxAge(0));

// Redirect to "/" while setting the cookies.
return HttpResponse.temporaryRedirect(URI.create("/"))
.cookies(cookies);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import com.objectcomputing.checkins.Environments;
import com.objectcomputing.checkins.services.memberprofile.MemberProfile;
import com.objectcomputing.checkins.services.memberprofile.currentuser.CurrentUserServices;
import com.objectcomputing.checkins.security.ImpersonationController;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.netty.cookies.NettyCookie;
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.event.ApplicationEventPublisher;
import io.micronaut.http.HttpRequest;
Expand Down Expand Up @@ -88,7 +91,10 @@ public Mono<Object> auth(HttpRequest<?> request, String email, String role) {
Authentication updatedAuth = Authentication.build(authentication.getName(), authentication.getRoles(), newAttributes);

eventPublisher.publishEvent(new LoginSuccessfulEvent(updatedAuth, null, Locale.getDefault()));
return loginHandler.loginSuccess(updatedAuth, request);

// Remove the original JWT on login.
return ((MutableHttpResponse)loginHandler.loginSuccess(updatedAuth, request))
.cookie(new NettyCookie(ImpersonationController.originalJWT, "").path("/").maxAge(0));
} else {
eventPublisher.publishEvent(new LoginFailedEvent(authenticationResponse, null, null, Locale.getDefault()));
return loginHandler.loginFailed(authenticationResponse, request);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public class EmailServicesImpl implements EmailServices {

private static final Logger LOG = LoggerFactory.getLogger(EmailServicesImpl.class);

private EmailSender htmlEmailSender;
private EmailSender textEmailSender;
private final EmailSender htmlEmailSender;
private final EmailSender textEmailSender;
private final CurrentUserServices currentUserServices;
private final MemberProfileRepository memberProfileRepository;
private final EmailRepository emailRepository;
Expand All @@ -43,14 +43,6 @@ public EmailServicesImpl(@Named(MailJetFactory.HTML_FORMAT) EmailSender htmlEmai
this.emailRepository = emailRepository;
}

public void setHtmlEmailSender(EmailSender emailSender) {
this.htmlEmailSender = emailSender;
}

public void setTextEmailSender(EmailSender emailSender) {
this.textEmailSender = emailSender;
}

@Override
public List<Email> sendAndSaveEmail(String subject, String content, boolean html, String... recipients) {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.objectcomputing.checkins.services.feedback.suggestions;

import com.objectcomputing.checkins.configuration.CheckInsConfiguration;
import com.objectcomputing.checkins.exceptions.PermissionException;
import com.objectcomputing.checkins.services.memberprofile.MemberProfile;
import com.objectcomputing.checkins.services.memberprofile.MemberProfileServices;
import com.objectcomputing.checkins.services.memberprofile.currentuser.CurrentUserServices;
import com.objectcomputing.checkins.services.team.member.TeamMember;
import com.objectcomputing.checkins.services.team.member.TeamMemberServices;
import io.micronaut.context.annotation.Property;
import jakarta.inject.Singleton;

import java.time.LocalDate;
Expand All @@ -19,24 +19,21 @@
import static com.objectcomputing.checkins.services.validate.PermissionsValidation.NOT_AUTHORIZED_MSG;

@Singleton
public class FeedbackSuggestionServiceImpl implements FeedbackSuggestionsService {

public static final String MAX_SUGGESTIONS = "check-ins.application.feedback.max-suggestions";
class FeedbackSuggestionServiceImpl implements FeedbackSuggestionsService {

private final MemberProfileServices memberProfileServices;
private final CurrentUserServices currentUserServices;
private final TeamMemberServices teamMemberServices;
private final Integer maxSuggestions;


public FeedbackSuggestionServiceImpl(MemberProfileServices memberProfileServices,
CurrentUserServices currentUserServices,
TeamMemberServices teamMemberServices,
@Property(name = MAX_SUGGESTIONS) Integer maxSuggestions) {
FeedbackSuggestionServiceImpl(MemberProfileServices memberProfileServices,
CurrentUserServices currentUserServices,
TeamMemberServices teamMemberServices,
CheckInsConfiguration checkInsConfiguration) {
this.memberProfileServices = memberProfileServices;
this.currentUserServices = currentUserServices;
this.teamMemberServices = teamMemberServices;
this.maxSuggestions = maxSuggestions;
this.maxSuggestions = checkInsConfiguration.getApplication().getFeedback().getMaxSuggestions();
}

@Override
Expand All @@ -50,49 +47,47 @@ public List<FeedbackSuggestionDTO> getSuggestionsByProfileId(UUID id) {
}

List<FeedbackSuggestionDTO> suggestions = new LinkedList<>();
if(suggestFor.getSupervisorid() != null && !suggestFor.getSupervisorid().equals(currentUser.getId()) && isMemberActive(suggestFor.getSupervisorid())) {
if (suggestFor.getSupervisorid() != null && !suggestFor.getSupervisorid().equals(currentUser.getId()) && isMemberActive(suggestFor.getSupervisorid())) {
suggestions.add(new FeedbackSuggestionDTO("Supervisor of requestee", suggestFor.getSupervisorid()));
}

if(suggestFor.getPdlId() != null && !suggestFor.getPdlId().equals(currentUser.getId()) && isMemberActive(suggestFor.getPdlId())) {
if (suggestFor.getPdlId() != null && !suggestFor.getPdlId().equals(currentUser.getId()) && isMemberActive(suggestFor.getPdlId())) {
suggestions.add(new FeedbackSuggestionDTO("PDL of requestee", suggestFor.getPdlId()));
}

Set<TeamMember> teamMemberships = teamMemberServices.findByFields(null, id, null);

for(TeamMember currentMembership: teamMemberships){
for (TeamMember currentMembership : teamMemberships) {
Set<TeamMember> teamMembers = teamMemberServices.findByFields(currentMembership.getTeamId(), null, null);
Set<TeamMember> leads = teamMembers.stream().filter(member -> member.isLead()).collect(Collectors.toSet());
Set<TeamMember> leads = teamMembers.stream().filter(TeamMember::isLead).collect(Collectors.toSet());
leads = filterTerminated(leads);
for(TeamMember lead: leads) {
if(suggestions.size() < maxSuggestions && !lead.getMemberId().equals(id) && !lead.getMemberId().equals(currentUserId)) {
for (TeamMember lead : leads) {
if (suggestions.size() < maxSuggestions && !lead.getMemberId().equals(id) && !lead.getMemberId().equals(currentUserId)) {
suggestions.add(new FeedbackSuggestionDTO("Team lead for requestee", lead.getMemberId()));
}
}

if(suggestions.size() >= maxSuggestions) break;
if (suggestions.size() >= maxSuggestions) break;
}

for(TeamMember currentMembership: teamMemberships){
for (TeamMember currentMembership : teamMemberships) {
Set<TeamMember> teamMembers = teamMemberServices.findByFields(currentMembership.getTeamId(), null, null);
teamMembers = teamMembers.stream().filter(member -> !member.isLead()).collect(Collectors.toSet());
teamMembers = filterTerminated(teamMembers);
for(TeamMember teamMember: teamMembers) {
if(suggestions.size() < maxSuggestions && !teamMember.getMemberId().equals(id) && !teamMember.getMemberId().equals(currentUserId)) {
for (TeamMember teamMember : teamMembers) {
if (suggestions.size() < maxSuggestions && !teamMember.getMemberId().equals(id) && !teamMember.getMemberId().equals(currentUserId)) {
suggestions.add(new FeedbackSuggestionDTO("Team member for requestee", teamMember.getMemberId()));
}
}

if(suggestions.size() >= maxSuggestions) break;
if (suggestions.size() >= maxSuggestions) break;
}

return suggestions;
}

private Set<TeamMember> filterTerminated(Set<TeamMember> suggestions) {
suggestions = suggestions.stream().filter((TeamMember suggestion) -> {
return isMemberActive(suggestion.getMemberId());
}).collect(Collectors.toSet());
suggestions = suggestions.stream().filter(suggestion -> isMemberActive(suggestion.getMemberId())).collect(Collectors.toSet());
return suggestions;
}

Expand Down
Loading

0 comments on commit efac159

Please sign in to comment.