Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.time.LocalDate;
import java.util.Set;
Expand All @@ -39,8 +36,6 @@
@ExecuteOn(TaskExecutors.BLOCKING)
@Tag(name = "pulse-responses")
public class PulseResponseController {
private static final Logger LOG = LoggerFactory.getLogger(PulseResponseController.class);

private final PulseResponseService pulseResponseServices;
private final MemberProfileServices memberProfileServices;
private final SlackSignatureVerifier slackSignatureVerifier;
Expand Down Expand Up @@ -153,15 +148,9 @@ public HttpResponse externalPulseResponse(
@Header("X-Slack-Request-Timestamp") String timestamp,
@Body String requestBody,
HttpRequest<?> request) {
// DEBUG Only
LOG.info(requestBody);

// Validate the request
if (slackSignatureVerifier.verifyRequest(signature,
timestamp, requestBody)) {
// DEBUG Only
LOG.info("Request has been verified");

// Convert the request body to a map of values.
FormUrlEncodedDecoder formUrlEncodedDecoder =
new FormUrlEncodedDecoder();
Expand All @@ -174,16 +163,14 @@ public HttpResponse externalPulseResponse(
PulseResponseCreateDTO pulseResponseDTO =
slackPulseResponseConverter.get(memberProfileServices,
(String)body.get(key));

// If we receive a null DTO, that means that this is not the
// actual submission of the form. We can just return 200 so
// that Slack knows to continue without error.
if (pulseResponseDTO == null) {
return HttpResponse.ok();
}

// DEBUG Only
LOG.info("Request has been converted");

// Create the pulse response
PulseResponse pulseResponse =
pulseResponseServices.unsecureSave(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ public PulseResponseCreateDTO get(
final Map<String, Object> values =
(Map<String, Object>)state.get("values");

dumpMap(values, "");

// Create the pulse DTO and fill in the values.
PulseResponseCreateDTO response = new PulseResponseCreateDTO();
response.setTeamMemberId(lookupUser(memberProfileServices, map));
Expand All @@ -59,7 +57,7 @@ public PulseResponseCreateDTO get(
internalBlock, "internalScore", "selected_option", true)));
// Internal Feelings
response.setInternalFeelings(getMappedValue(
values, "internaltext", "internalFeelings", false));
values, "internalText", "internalFeelings", false));

// External Score
Map<String, Object> externalBlock =
Expand Down Expand Up @@ -126,15 +124,4 @@ private UUID lookupUser(MemberProfileServices memberProfileServices,
MemberProfile member = memberProfileServices.findByWorkEmail(email);
return member.getId();
}

// DEBUG Only
private void dumpMap(Map<?, ?> map, String indent) {
for (Map.Entry<?, ?> entry : map.entrySet()) {
LOG.info(indent + entry.getKey() + " : " + entry.getValue());

if (entry.getValue() instanceof Map) {
dumpMap((Map<?, ?>) entry.getValue(), indent + " ");
}
}
}
}
2 changes: 1 addition & 1 deletion server/src/main/resources/db/dev/R__Load_testing_data.sql
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ VALUES
INSERT INTO member_profile -- Unreal Ulysses
(id, firstName, lastName, title, pdlid, location, workEmail, employeeid, startdate, biotext, supervisorid, birthDate, last_seen)
VALUES
('dfe2f986-fac0-11eb-9a03-0242ac130003', PGP_SYM_ENCRYPT('Unreal','${aeskey}'), PGP_SYM_ENCRYPT('Ulysses','${aeskey}'), PGP_SYM_ENCRYPT('Test Engineer 2','${aeskey}'), '1c813446-c65a-4f49-b980-0193f7bfff8c', PGP_SYM_ENCRYPT('St. Louis','${aeskey}'), PGP_SYM_ENCRYPT('testing2@objectcomputing.com','${aeskey}'), '010101012', '2021-05-22', PGP_SYM_ENCRYPT('Test user 2','${aeskey}'), 'e4b2fe52-1915-4544-83c5-21b8f871f6db', '1950-02-01', '2021-05-22');
('dfe2f986-fac0-11eb-9a03-0242ac130003', PGP_SYM_ENCRYPT('Unreal','${aeskey}'), PGP_SYM_ENCRYPT('Ulysses','${aeskey}'), PGP_SYM_ENCRYPT('Test Engineer 2','${aeskey}'), '1c813446-c65a-4f49-b980-0193f7bfff8c', PGP_SYM_ENCRYPT('St. Louis','${aeskey}'), PGP_SYM_ENCRYPT('testing2@objectcomputing.com','${aeskey}'), '010101012', '2021-05-22', PGP_SYM_ENCRYPT('Test user 2','${aeskey}'), 'e4b2fe52-1915-4544-83c5-21b8f871f6db', '1950-01-01', '2021-05-22');

INSERT INTO member_profile -- Kazuhira Miller
(id, firstName, lastName, title, pdlid, location, workEmail, employeeid, startdate, biotext, supervisorid, birthDate, last_seen)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.objectcomputing.checkins.services;

import com.objectcomputing.checkins.notifications.social_media.SlackSearch;
import com.objectcomputing.checkins.configuration.CheckInsConfiguration;

import io.micronaut.context.annotation.Replaces;
import io.micronaut.context.annotation.Requires;
import io.micronaut.core.util.StringUtils;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;

import jakarta.inject.Singleton;

import java.util.HashMap;
import java.util.Map;

@Singleton
@Replaces(SlackSearch.class)
@Requires(property = "replace.slacksearch", value = StringUtils.TRUE)
public class SlackSearchReplacement extends SlackSearch {
public final Map<String, String> channels = new HashMap<>();
public final Map<String, String> users = new HashMap<>();

public SlackSearchReplacement(CheckInsConfiguration checkInsConfiguration) {
super(checkInsConfiguration);
}

@Override
public String findChannelId(String channelName) {
return channels.containsKey(channelName) ?
channels.get(channelName) : null;
}

@Override
public String findUserEmail(String userId) {
return users.containsKey(userId) ? users.get(userId) : null;
}

@Override
public String findUserId(String userEmail) {
for (Map.Entry<String, String> entry : users.entrySet()) {
if (entry.getValue().equals(userEmail)) {
return entry.getKey();
}
}
return null;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
import com.objectcomputing.checkins.services.fixture.RoleFixture;
import com.objectcomputing.checkins.services.memberprofile.MemberProfile;
import com.objectcomputing.checkins.util.Util;
import com.objectcomputing.checkins.configuration.CheckInsConfiguration;
import com.objectcomputing.checkins.services.SlackSearchReplacement;

import io.micronaut.core.util.StringUtils;
import io.micronaut.core.type.Argument;
import io.micronaut.context.annotation.Property;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
Expand All @@ -27,19 +32,31 @@
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.time.Instant;

import static com.objectcomputing.checkins.services.role.RoleType.Constants.ADMIN_ROLE;
import static com.objectcomputing.checkins.services.role.RoleType.Constants.MEMBER_ROLE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

@Property(name = "replace.slacksearch", value = StringUtils.TRUE)
class PulseResponseControllerTest extends TestContainersSuite implements MemberProfileFixture, RoleFixture, PulseResponseFixture {

@Inject
@Client("/services/pulse-responses")
protected HttpClient client;

@Inject
private CheckInsConfiguration configuration;

@Inject
private SlackSearchReplacement slackSearch;

private Map<String, MemberProfile> hierarchy;

@BeforeEach
Expand Down Expand Up @@ -516,6 +533,50 @@ void testUpdateInvalidDatePulseResponse() {
assertEquals(request.getPath(), href);
}

@Test
void testCreateAPulseResponseFromSlack() {
MemberProfile memberProfile = createADefaultMemberProfile();
slackSearch.users.put("SLACK_ID_HI", memberProfile.getWorkEmail());

final String rawBody = "payload=%7B%22type%22%3A+%22view_submission%22%2C+%22user%22%3A+%7B%22id%22%3A+%22SLACK_ID_HI%22%7D%2C+%22view%22%3A+%7B%22id%22%3A+%22VNHU13V36%22%2C+%22type%22%3A+%22modal%22%2C+%22state%22%3A+%7B%22values%22%3A+%7B%22internalNumber%22%3A+%7B%22internalScore%22%3A+%7B%22selected_option%22%3A+%7B%22type%22%3A+%22radio_buttons%22%2C+%22value%22%3A+%224%22%7D%7D%7D%2C+%22internalText%22%3A+%7B%22internalFeelings%22%3A+%7B%22type%22%3A+%22plain_text_input%22%2C+%22value%22%3A+%22I+am+a+robot.%22%7D%7D%2C+%22externalNumber%22%3A+%7B%22externalScore%22%3A+%7B%22selected_option%22%3A+%7B%22type%22%3A+%22radio_buttons%22%2C+%22value%22%3A+%225%22%7D%7D%7D%2C+%22externalText%22%3A+%7B%22externalFeelings%22%3A+%7B%22type%22%3A+%22plain_text_input%22%2C+%22value%22%3A+%22You+are+a+robot.%22%7D%7D%7D%7D%7D%7D";

long currentTime = Instant.now().getEpochSecond();
String timestamp = String.valueOf(currentTime);

final HttpRequest request = HttpRequest.POST("/external", rawBody)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("X-Slack-Signature", slackSignature(timestamp, rawBody))
.header("X-Slack-Request-Timestamp", timestamp);

final HttpResponse response = client.toBlocking().exchange(request);

assertEquals(HttpStatus.OK, response.getStatus());
}

private String slackSignature(String timestamp, String rawBody) {
String baseString = "v0:" + timestamp + ":" + rawBody;
String secret = configuration.getApplication()
.getPulseResponse()
.getSlack().getSigningSecret();

try {
// Generate HMAC SHA-256 signature
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
mac.init(secretKeySpec);
byte[] hash = mac.doFinal(baseString.getBytes(StandardCharsets.UTF_8));

// Convert hash to hex
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
hexString.append(String.format("%02x", b));
}
return "v0=" + hexString.toString();
} catch (Exception e) {
return null;
}
}

private static PulseResponseCreateDTO createPulseResponseCreateDTO() {
return createPulseResponseCreateDTO(UUID.randomUUID());
}
Expand Down
Loading