Skip to content

Commit 076f81a

Browse files
committed
2024-10-30 - feedback - external reviewer - server-side
1 parent fd2ee9d commit 076f81a

25 files changed

+563
-44
lines changed

server/src/main/java/com/objectcomputing/checkins/security/HomeController.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
package com.objectcomputing.checkins.security;
22

3+
import com.objectcomputing.checkins.services.feedback_request.FeedbackRequestExternalRecipientController;
34
import io.micronaut.context.env.Environment;
5+
import io.micronaut.http.HttpResponse;
6+
import io.micronaut.http.MediaType;
47
import io.micronaut.http.annotation.Controller;
58
import io.micronaut.http.annotation.Get;
9+
import io.micronaut.http.annotation.Produces;
610
import io.micronaut.http.server.types.files.StreamedFile;
711
import io.micronaut.security.annotation.Secured;
812
import io.micronaut.security.rules.SecurityRule;
913
import io.micronaut.views.View;
1014

1115
import io.micronaut.core.annotation.Nullable;
16+
import org.slf4j.Logger;
17+
import org.slf4j.LoggerFactory;
18+
1219
import java.security.Principal;
1320
import java.util.HashMap;
1421
import java.util.Map;
@@ -19,6 +26,7 @@
1926
public class HomeController {
2027

2128
private final Environment environment;
29+
private static final Logger LOG = LoggerFactory.getLogger(HomeController.class);
2230

2331
public HomeController(Environment environment) {
2432
this.environment = environment;
@@ -34,8 +42,11 @@ public Map<String, Object> forbidden(@Nullable Principal principal) {
3442
* Forwards any unmapped paths (except those containing a period) to the client {@code index.html}.
3543
* @return forward to client {@code index.html}.
3644
*/
37-
@Get("/{path:[^\\.]*}")
45+
// 2024-10-29 - Note the path excludes "/feedbackExternalRecipient", which is handled by HomeExternalRecipientController
46+
@Get("/{path:^(?!feedbackExternalRecipient)([^\\.]+)$}")
3847
public Optional<StreamedFile> forward(String path) {
48+
LOG.info("HomeController, forward, path: " + path);
3949
return environment.getResource("public/index.html").map(StreamedFile::new);
4050
}
51+
4152
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.objectcomputing.checkins.security;
2+
3+
import io.micronaut.context.env.Environment;
4+
import io.micronaut.core.annotation.Nullable;
5+
import io.micronaut.http.HttpResponse;
6+
import io.micronaut.http.MediaType;
7+
import io.micronaut.http.annotation.Controller;
8+
import io.micronaut.http.annotation.Get;
9+
import io.micronaut.http.annotation.Produces;
10+
import io.micronaut.http.server.types.files.StreamedFile;
11+
import io.micronaut.security.annotation.Secured;
12+
import io.micronaut.security.rules.SecurityRule;
13+
import io.micronaut.views.View;
14+
import org.slf4j.Logger;
15+
import org.slf4j.LoggerFactory;
16+
17+
import java.security.Principal;
18+
import java.util.HashMap;
19+
import java.util.Map;
20+
import java.util.Optional;
21+
22+
@Controller
23+
@Secured(SecurityRule.IS_ANONYMOUS)
24+
public class HomeExternalRecipientController {
25+
26+
private final Environment environment;
27+
private static final Logger LOG = LoggerFactory.getLogger(HomeExternalRecipientController.class);
28+
29+
public HomeExternalRecipientController(Environment environment) {
30+
this.environment = environment;
31+
}
32+
33+
@Get("/feedbackExternalRecipient/{path:.+}")
34+
public Optional<StreamedFile> forward(String path) {
35+
LOG.info("HomeExternalRecipientController, forward, path: " + path);
36+
return environment.getResource("public/index_external_recipient.html").map(StreamedFile::new);
37+
}
38+
39+
}

server/src/main/java/com/objectcomputing/checkins/services/feedback_answer/FeedbackAnswerController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public HttpResponse<FeedbackAnswerResponseDTO> getById(UUID id) {
8282
@Get("/{?questionId,requestId}")
8383
@RequiredPermission(Permission.CAN_VIEW_FEEDBACK_ANSWER)
8484
public List<FeedbackAnswerResponseDTO> findByValues(@Nullable UUID questionId, @Nullable UUID requestId) {
85-
return feedbackAnswerServices.findByValues(questionId, requestId)
85+
return feedbackAnswerServices.findByValues(questionId, requestId, null)
8686
.stream()
8787
.map(this::fromEntity)
8888
.toList();
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package com.objectcomputing.checkins.services.feedback_answer;
2+
3+
import com.objectcomputing.checkins.exceptions.BadArgException;
4+
import com.objectcomputing.checkins.services.feedback_request.FeedbackRequest;
5+
import com.objectcomputing.checkins.services.feedback_request.FeedbackRequestServices;
6+
import com.objectcomputing.checkins.services.permissions.Permission;
7+
import com.objectcomputing.checkins.services.permissions.RequiredPermission;
8+
import io.micronaut.core.annotation.Nullable;
9+
import io.micronaut.http.HttpResponse;
10+
import io.micronaut.http.annotation.*;
11+
import io.micronaut.scheduling.TaskExecutors;
12+
import io.micronaut.scheduling.annotation.ExecuteOn;
13+
import io.micronaut.security.annotation.Secured;
14+
import io.micronaut.security.rules.SecurityRule;
15+
import jakarta.validation.Valid;
16+
import jakarta.validation.constraints.NotNull;
17+
18+
import java.net.URI;
19+
import java.util.List;
20+
import java.util.UUID;
21+
22+
@Controller("/services/feedback/answers/external/recipients")
23+
@ExecuteOn(TaskExecutors.BLOCKING)
24+
@Secured(SecurityRule.IS_ANONYMOUS)
25+
public class FeedbackAnswerExternalRecipientController {
26+
27+
private final FeedbackAnswerServices feedbackAnswerServices;
28+
private final FeedbackRequestServices feedbackRequestServices;
29+
30+
public FeedbackAnswerExternalRecipientController(FeedbackAnswerServices feedbackAnswerServices, FeedbackRequestServices feedbackRequestServices) {
31+
this.feedbackAnswerServices = feedbackAnswerServices;
32+
this.feedbackRequestServices = feedbackRequestServices;
33+
}
34+
35+
/**
36+
* Create a feedback answer
37+
*
38+
* @param requestBody {@link FeedbackAnswerCreateDTO} New feedback answer to create
39+
* @return {@link FeedbackAnswerResponseDTO}
40+
*/
41+
@Post
42+
public HttpResponse<FeedbackAnswerResponseDTO> save(@Body @Valid @NotNull FeedbackAnswerCreateDTO requestBody) {
43+
FeedbackRequest feedbackRequest = this.feedbackRequestServices.getById(requestBody.getRequestId());
44+
if (feedbackRequest.getExternalRecipientId() == null) {
45+
throw new BadArgException("This feedback request is not for an external recipient");
46+
}
47+
FeedbackAnswer savedAnswer = feedbackAnswerServices.save(fromDTO(requestBody));
48+
return HttpResponse.created(fromEntity(savedAnswer))
49+
.headers(headers -> headers.location(URI.create("/feedback_answer/" + savedAnswer.getId())));
50+
}
51+
52+
/**
53+
* Update a feedback answer
54+
*
55+
* @param requestBody {@link FeedbackAnswerUpdateDTO} The updated feedback answer
56+
* @return {@link FeedbackAnswerResponseDTO}
57+
*/
58+
@Put
59+
public HttpResponse<FeedbackAnswerResponseDTO> update(@Body @Valid @NotNull FeedbackAnswerUpdateDTO requestBody) {
60+
FeedbackAnswer feedbackAnswerExistingRecord = this.feedbackAnswerServices.getById(requestBody.getId());
61+
FeedbackRequest feedbackRequest = this.feedbackRequestServices.getById(feedbackAnswerExistingRecord.getRequestId());
62+
if (feedbackRequest.getExternalRecipientId() == null) {
63+
throw new BadArgException("This feedback request is not for an external recipient");
64+
}
65+
FeedbackAnswer savedAnswer = feedbackAnswerServices.update(fromDTO(requestBody));
66+
return HttpResponse.ok(fromEntity(savedAnswer))
67+
.headers(headers -> headers.location(URI.create("/feedback_answer/" + savedAnswer.getId())));
68+
}
69+
70+
/**
71+
* Get a feedback answer by ID
72+
*
73+
* @param id {@link UUID} ID of the feedback answer
74+
* @return {@link FeedbackAnswerResponseDTO}
75+
*/
76+
@Get("/{id}")
77+
public HttpResponse<FeedbackAnswerResponseDTO> getById(UUID id) {
78+
FeedbackAnswer savedAnswer = feedbackAnswerServices.getById(id);
79+
FeedbackRequest feedbackRequest = this.feedbackRequestServices.getById(savedAnswer.getRequestId());
80+
if (feedbackRequest.getExternalRecipientId() == null) {
81+
throw new BadArgException("This feedback request is not for an external recipient");
82+
}
83+
return HttpResponse.ok(fromEntity(savedAnswer))
84+
.headers(headers -> headers.location(URI.create("/feedback_answer/" + savedAnswer.getId())));
85+
}
86+
87+
/**
88+
* Search for all feedback requests that match the intersection of the provided values
89+
* Any values that are null are not applied to the intersection
90+
*
91+
* @param questionId The attached {@link UUID} of the related question
92+
* @param requestId The attached {@link UUID} of the request that corresponds with the answer
93+
* @param externalRecipientId The attached {@link UUID} of the external-recipient that corresponds with the answer
94+
* @return {@link FeedbackAnswerResponseDTO}
95+
*/
96+
@Get("/{?questionId,requestId,externalRecipientId}")
97+
public List<FeedbackAnswerResponseDTO> findByValues(@Nullable UUID questionId, @Nullable UUID requestId, @Nullable UUID externalRecipientId) {
98+
return feedbackAnswerServices.findByValues(questionId, requestId, externalRecipientId)
99+
.stream()
100+
.map(this::fromEntity)
101+
.toList();
102+
}
103+
104+
private FeedbackAnswer fromDTO(FeedbackAnswerCreateDTO dto) {
105+
return new FeedbackAnswer(dto.getAnswer(), dto.getQuestionId(), dto.getRequestId(), dto.getSentiment());
106+
}
107+
108+
private FeedbackAnswer fromDTO(FeedbackAnswerUpdateDTO dto) {
109+
return new FeedbackAnswer(dto.getId(), dto.getAnswer(), dto.getSentiment());
110+
}
111+
112+
private FeedbackAnswerResponseDTO fromEntity(FeedbackAnswer feedbackAnswer) {
113+
FeedbackAnswerResponseDTO dto = new FeedbackAnswerResponseDTO();
114+
dto.setId(feedbackAnswer.getId());
115+
dto.setAnswer(feedbackAnswer.getAnswer());
116+
dto.setQuestionId(feedbackAnswer.getQuestionId());
117+
dto.setRequestId(feedbackAnswer.getRequestId());
118+
dto.setSentiment(feedbackAnswer.getSentiment());
119+
return dto;
120+
}
121+
}

server/src/main/java/com/objectcomputing/checkins/services/feedback_answer/FeedbackAnswerRepository.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,17 @@ public interface FeedbackAnswerRepository extends CrudRepository<FeedbackAnswer,
2323
@Override
2424
<S extends FeedbackAnswer> S update(@NotNull @Valid S entity);
2525

26-
@Query("SELECT id, PGP_SYM_DECRYPT(cast(answer as bytea), '${aes.key}') as answer, question_id, request_id, sentiment FROM feedback_answers WHERE (:questionId IS NULL OR question_id = :questionId) AND (request_id = :requestId)")
26+
@Query("SELECT id, PGP_SYM_DECRYPT(cast(answer as bytea), '${aes.key}') as answer, question_id, request_id, sentiment " +
27+
"FROM feedback_answers WHERE (:questionId IS NULL OR question_id = :questionId) AND (request_id = :requestId)")
2728
List<FeedbackAnswer> getByQuestionIdAndRequestId(@Nullable String questionId, @Nullable String requestId);
2829

30+
@Query("SELECT FA.id, PGP_SYM_DECRYPT(cast(FA.answer as bytea), '${aes.key}') as answer, FA.question_id, FA.request_id, FA.sentiment " +
31+
"FROM feedback_answers FA JOIN feedback_requests FR on FA.request_id = FR.id " +
32+
"WHERE 1=1 " +
33+
"and (:questionId IS NULL OR question_id = :questionId) " +
34+
"AND (request_id = :requestId) " +
35+
"AND :externalRecipientId = FR.external_recipient_id " +
36+
"")
37+
List<FeedbackAnswer> getByQuestionIdAndRequestId(@Nullable String questionId, @Nullable String requestId, @NotNull String externalRecipientId);
38+
2939
}

server/src/main/java/com/objectcomputing/checkins/services/feedback_answer/FeedbackAnswerServices.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ public interface FeedbackAnswerServices {
1313

1414
FeedbackAnswer getById(UUID id);
1515

16-
List<FeedbackAnswer> findByValues(@Nullable UUID questionId, @Nullable UUID requestId);
16+
List<FeedbackAnswer> findByValues(@Nullable UUID questionId, @Nullable UUID requestId, @Nullable UUID externalRecipientId);
1717
}

server/src/main/java/com/objectcomputing/checkins/services/feedback_answer/FeedbackAnswerServicesImpl.java

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,20 @@ public FeedbackAnswer getById(UUID id) {
9898
}
9999

100100
@Override
101-
public List<FeedbackAnswer> findByValues(@Nullable UUID questionId, @Nullable UUID requestId) {
101+
public List<FeedbackAnswer> findByValues(@Nullable UUID questionId, @Nullable UUID requestId, @Nullable UUID externalRecipientId) {
102102
List<FeedbackAnswer> response = new ArrayList<>();
103103
FeedbackRequest feedbackRequest;
104-
UUID currentUserId = currentUserServices.getCurrentUser().getId();
104+
MemberProfile currentUser;
105+
106+
try {
107+
currentUser = currentUserServices.getCurrentUser();
108+
109+
} catch (NotFoundException e) {
110+
currentUser = null;
111+
112+
}
113+
final UUID currentUserId = (currentUser != null) ? currentUser.getId() : null;
114+
105115
try {
106116
feedbackRequest = feedbackRequestServices.getById(requestId);
107117
} catch (NotFoundException e) {
@@ -110,16 +120,18 @@ public List<FeedbackAnswer> findByValues(@Nullable UUID questionId, @Nullable UU
110120
final UUID requestCreatorId = feedbackRequest.getCreatorId();
111121
final UUID requesteeId = feedbackRequest.getRequesteeId();
112122
final UUID recipientId = feedbackRequest.getRecipientId();
113-
boolean isRequesteesSupervisor = requesteeId != null ? memberProfileServices.getSupervisorsForId(requesteeId).stream().anyMatch(profile -> currentUserId.equals(profile.getId())) : false;
123+
boolean isRequesteesSupervisor = requesteeId != null && currentUserId != null ? memberProfileServices.getSupervisorsForId(requesteeId).stream().anyMatch(profile -> currentUserId.equals(profile.getId())) : false;
114124
MemberProfile requestee = memberProfileServices.getById(requesteeId);
115125
final UUID requesteePDL = requestee.getPdlId();
116-
if (currentUserServices.isAdmin() || currentUserId.equals(requesteePDL) || isRequesteesSupervisor || requestCreatorId.equals(currentUserId) || recipientId.equals(currentUserId)) {
126+
if (currentUserServices.isAdmin() || (currentUserId != null && currentUserId.equals(requesteePDL)) || isRequesteesSupervisor || requestCreatorId.equals(currentUserId) || (recipientId != null && recipientId.equals(currentUserId))) {
117127
response.addAll(feedbackAnswerRepository.getByQuestionIdAndRequestId(Util.nullSafeUUIDToString(questionId), Util.nullSafeUUIDToString(requestId)));
118128
return response;
129+
} else if (externalRecipientId != null) {
130+
response.addAll(feedbackAnswerRepository.getByQuestionIdAndRequestId(Util.nullSafeUUIDToString(questionId), Util.nullSafeUUIDToString(requestId), externalRecipientId.toString()));
131+
return response;
119132
}
120133

121134
throw new PermissionException(NOT_AUTHORIZED_MSG);
122-
123135
}
124136

125137
public FeedbackRequest getRelatedFeedbackRequest(FeedbackAnswer feedbackAnswer) {
@@ -136,24 +148,48 @@ public FeedbackRequest getRelatedFeedbackRequest(FeedbackAnswer feedbackAnswer)
136148

137149
public boolean createIsPermitted(FeedbackRequest feedbackRequest) {
138150
final UUID recipientId = feedbackRequest.getRecipientId();
139-
final UUID currentUserId = currentUserServices.getCurrentUser().getId();
140-
return recipientId.equals(currentUserId);
151+
MemberProfile currentUser;
152+
UUID currentUserId;
153+
154+
try {
155+
currentUser = currentUserServices.getCurrentUser();
156+
currentUserId = currentUser.getId();
157+
} catch (NotFoundException e) {
158+
currentUser = null;
159+
currentUserId = null;
160+
}
161+
return (recipientId != null && recipientId.equals(currentUserId)) || (currentUserId == null && feedbackRequest.getExternalRecipientId() != null);
141162
}
142163

143164
public boolean updateIsPermitted(FeedbackRequest feedbackRequest) {
144165
return createIsPermitted(feedbackRequest);
145166
}
146167

147168
public boolean getIsPermitted(FeedbackRequest feedbackRequest) {
169+
MemberProfile currentUser;
170+
171+
try {
172+
currentUser = currentUserServices.getCurrentUser();
173+
} catch (NotFoundException e) {
174+
currentUser = null;
175+
}
176+
final UUID currentUserId = (currentUser != null) ? currentUserServices.getCurrentUser().getId() : null;
177+
148178
final boolean isAdmin = currentUserServices.isAdmin();
149179
final UUID requestCreatorId = feedbackRequest.getCreatorId();
150180
UUID requesteeId = feedbackRequest.getRequesteeId();
151181
MemberProfile requestee = memberProfileServices.getById(requesteeId);
152-
final UUID currentUserId = currentUserServices.getCurrentUser().getId();
153182
final UUID recipientId = feedbackRequest.getRecipientId();
154-
boolean isRequesteesSupervisor = requesteeId != null ? memberProfileServices.getSupervisorsForId(requesteeId).stream().anyMatch(profile -> currentUserId.equals(profile.getId())) : false;
183+
boolean isRequesteesSupervisor;
184+
185+
if (requesteeId != null && currentUserId != null) {
186+
isRequesteesSupervisor = memberProfileServices.getSupervisorsForId(requesteeId).stream().anyMatch(profile -> currentUserId.equals(profile.getId()));
187+
} else {
188+
isRequesteesSupervisor = false;
189+
}
190+
155191
final UUID requesteePDL = requestee.getPdlId();
156192

157-
return isAdmin || currentUserId.equals(requesteePDL) || isRequesteesSupervisor || requestCreatorId.equals(currentUserId) || recipientId.equals(currentUserId);
193+
return isAdmin || (currentUserId != null && currentUserId.equals(requesteePDL)) || isRequesteesSupervisor || requestCreatorId.equals(currentUserId) || (recipientId != null && recipientId.equals(currentUserId)) || feedbackRequest.getExternalRecipientId()!= null;
158194
}
159195
}

server/src/main/java/com/objectcomputing/checkins/services/feedback_answer/question_and_answer/QuestionAndAnswerServicesImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public List<Tuple> getAllQuestionsAndAnswers(UUID requestId) {
4646
throw new PermissionException(NOT_AUTHORIZED_MSG);
4747
}
4848
List<TemplateQuestion> templateQuestions = templateQuestionServices.findByFields(feedbackRequest.getTemplateId());
49-
List<FeedbackAnswer> answerList = feedbackAnswerServices.findByValues(null, requestId);
49+
List<FeedbackAnswer> answerList = feedbackAnswerServices.findByValues(null, requestId, null);
5050

5151
List<Tuple> returnerList = new ArrayList<>();
5252

@@ -86,7 +86,7 @@ public Tuple getQuestionAndAnswer(@Nullable UUID requestId, @Nullable UUID quest
8686
}
8787

8888
List<FeedbackAnswer> list;
89-
list = feedbackAnswerServices.findByValues(questionId, requestId);
89+
list = feedbackAnswerServices.findByValues(questionId, requestId, null);
9090

9191
FeedbackAnswer returnedAnswer;
9292
if (list.isEmpty()) {

server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestController.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ public void delete(@NotNull UUID id) {
9292
* @param id {@link UUID} ID of the request
9393
* @return {@link FeedbackRequestResponseDTO}
9494
*/
95-
//@Secured(SecurityRule.IS_ANONYMOUS)
9695
@Get("/{id}")
9796
@RequiredPermission(Permission.CAN_VIEW_FEEDBACK_REQUEST)
9897
public HttpResponse<FeedbackRequestResponseDTO> getById(UUID id) {

0 commit comments

Comments
 (0)