package fi.otavanopisto.muikku.plugins.schooldatapyramus.webhook;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.event.Event;
import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.Transactional;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import fi.otavanopisto.muikku.controller.PluginSettingsController;
import fi.otavanopisto.muikku.plugins.schooldatapyramus.PyramusUpdater;
import fi.otavanopisto.muikku.schooldata.SchoolDataBridgeSessionController;
import fi.otavanopisto.pyramus.webhooks.data.WebhookCourseData;
import fi.otavanopisto.pyramus.webhooks.data.WebhookCourseStaffMemberData;
import fi.otavanopisto.pyramus.webhooks.data.WebhookCourseStudentData;
import fi.otavanopisto.pyramus.webhooks.data.WebhookPersonData;
import fi.otavanopisto.pyramus.webhooks.data.WebhookStaffMemberData;
import fi.otavanopisto.pyramus.webhooks.data.WebhookStudentData;
import fi.otavanopisto.pyramus.webhooks.data.WebhookStudentGroupData;
import fi.otavanopisto.pyramus.webhooks.data.WebhookStudentGroupStaffMemberData;
import fi.otavanopisto.pyramus.webhooks.data.WebhookStudentGroupStudentData;
@WebServlet (urlPatterns = "/pyramus/webhook")
@Transactional
public class PyramusWebhookServlet extends HttpServlet {
private static final long serialVersionUID = 6706786035217090492L;
@Inject
private Logger logger;
@Inject
private Event<WebhookNotificationEvent> webhookNotificationEvent;
@Inject
private PluginSettingsController pluginSettingsController;
@Inject
private PyramusUpdater pyramusUpdater;
@Inject
private SchoolDataBridgeSessionController schoolDataBridgeSessionController;
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String requestSignature = req.getHeader("X-Pyramus-Signature");
if (StringUtils.isBlank(requestSignature)) {
logger.log(Level.WARNING, "Invalid webhook requrest (no X-Pyramus-Signature header found)");
resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
String webhookSecret = pluginSettingsController.getPluginSetting("school-data-pyramus", "webhook.secret");
if (StringUtils.isBlank(webhookSecret)) {
logger.log(Level.WARNING, "Webhook secret is not configured");
resp.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
String expectedSignature = DigestUtils.md5Hex(webhookSecret);
if (!StringUtils.equals(requestSignature, expectedSignature)) {
logger.log(Level.WARNING, "Could not authorize webhook (expected signature does not match request signature)");
resp.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
ObjectMapper objectMapper = new ObjectMapper();
PyramusWebhookPayload payload = objectMapper.readValue(req.getInputStream(), PyramusWebhookPayload.class);
if (payload.getType() == null) {
logger.log(Level.WARNING, "Invalid webhook payload (type is missing or is invalid)");
resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
if (StringUtils.isBlank(payload.getData())) {
logger.log(Level.WARNING, "Invalid webhook payload (data is missing)");
resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
webhookNotificationEvent.fire(new WebhookNotificationEvent(payload.getType(), payload.getData()));
schoolDataBridgeSessionController.startSystemSession();
try {
logger.log(Level.INFO, String.format("Received a webhook notification of type %s", payload.getType().toString()));
switch (payload.getType()) {
case COURSE_CREATE:
case COURSE_UPDATE:
case COURSE_ARCHIVE:
WebhookCourseData courseData = unmarshalData(resp, payload, WebhookCourseData.class);
if (courseData == null) {
return;
}
pyramusUpdater.updateCourse(courseData.getCourseId());
break;
case COURSE_STAFF_MEMBER_CREATE:
case COURSE_STAFF_MEMBER_UPDATE:
case COURSE_STAFF_MEMBER_DELETE:
WebhookCourseStaffMemberData courseStaffMemberData = unmarshalData(resp, payload, WebhookCourseStaffMemberData.class);
if (courseStaffMemberData == null) {
return;
}
pyramusUpdater.updateCourseStaffMember(courseStaffMemberData.getCourseStaffMemberId(), courseStaffMemberData.getCourseId(), courseStaffMemberData.getStaffMemberId());
case STAFF_MEMBER_CREATE:
case STAFF_MEMBER_UPDATE:
case STAFF_MEMBER_DELETE:
WebhookStaffMemberData staffMemberData = unmarshalData(resp, payload, WebhookStaffMemberData.class);
if (staffMemberData == null) {
return;
}
pyramusUpdater.updateStaffMember(staffMemberData.getStaffMemberId());
break;
case STUDENT_CREATE:
case STUDENT_UPDATE:
case STUDENT_ARCHIVE:
WebhookStudentData studentData = unmarshalData(resp, payload, WebhookStudentData.class);
if (studentData == null) {
return;
}
pyramusUpdater.updateStudent(studentData.getStudentId());
break;
case COURSE_STUDENT_CREATE:
case COURSE_STUDENT_UPDATE:
case COURSE_STUDENT_ARCHIVE:
WebhookCourseStudentData courseStudentData = unmarshalData(resp, payload, WebhookCourseStudentData.class);
if (courseStudentData == null) {
return;
}
pyramusUpdater.updateCourseStudent(courseStudentData.getCourseStudentId(), courseStudentData.getCourseId(), courseStudentData.getStudentId());
break;
case PERSON_ARCHIVE:
case PERSON_CREATE:
case PERSON_UPDATE:
WebhookPersonData personData = unmarshalData(resp, payload, WebhookPersonData.class);
if (personData == null) {
return;
}
pyramusUpdater.updatePerson(personData.getPersonId());
break;
case STUDENTGROUP_ARCHIVE:
case STUDENTGROUP_CREATE:
case STUDENTGROUP_UPDATE:
WebhookStudentGroupData groupData = unmarshalData(resp, payload, WebhookStudentGroupData.class);
if (groupData == null) {
return;
}
pyramusUpdater.updateStudentGroup(groupData.getStudentGroupId());
break;
case STUDENTGROUP_STAFFMEMBER_CREATE:
case STUDENTGROUP_STAFFMEMBER_REMOVE:
case STUDENTGROUP_STAFFMEMBER_UPDATE:
WebhookStudentGroupStaffMemberData studentGroupStaffMemberData = unmarshalData(resp, payload, WebhookStudentGroupStaffMemberData.class);
if (studentGroupStaffMemberData == null) {
return;
}
pyramusUpdater.updateStudentGroupStaffMember(studentGroupStaffMemberData.getStudentGroupId(), studentGroupStaffMemberData.getStudentGroupUserId());
break;
case STUDENTGROUP_STUDENT_CREATE:
case STUDENTGROUP_STUDENT_REMOVE:
case STUDENTGROUP_STUDENT_UPDATE:
WebhookStudentGroupStudentData studentGroupStudentData = unmarshalData(resp, payload, WebhookStudentGroupStudentData.class);
if (studentGroupStudentData == null) {
return;
}
pyramusUpdater.updateStudentGroupStudent(studentGroupStudentData.getStudentGroupId(), studentGroupStudentData.getStudentGroupUserId());
break;
default:
logger.log(Level.WARNING, "Unknown webhook type " + payload.getType());
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
return;
}
} finally {
schoolDataBridgeSessionController.endSystemSession();
}
}
private <T> T unmarshalData(HttpServletResponse resp, PyramusWebhookPayload payload, Class<? extends T> type) throws IOException, JsonParseException, JsonMappingException {
T data = new ObjectMapper().readValue(payload.getData(), type);
if (data == null) {
logger.log(Level.WARNING, "Invalid webhook payload (could not unmarshal data)");
resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
}
return data;
}
}