/** * Licensed to The Apereo Foundation under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * * The Apereo Foundation licenses this file to you under the Educational * Community License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License * at: * * http://opensource.org/licenses/ecl2.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * */ package org.opencastproject.lti; import org.apache.commons.lang3.StringUtils; import org.json.simple.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** * A servlet to accept an LTI login via POST. The actual authentication happens in LtiProcessingFilter. GET requests * produce JSON containing the LTI parameters passed during LTI launch. */ public class LtiServlet extends HttpServlet { private static final String LTI_CUSTOM_PREFIX = "custom_"; /** The logger */ private static final Logger logger = LoggerFactory.getLogger(LtiServlet.class); /** The serialization uid */ private static final long serialVersionUID = 6138043870346176520L; /** The key used to store the LTI attributes in the HTTP session */ public static final String SESSION_ATTRIBUTE_KEY = "org.opencastproject.lti.LtiServlet"; /** See the LTI specification */ public static final String LTI_MESSAGE_TYPE = "lti_message_type"; /** See the LTI specification */ public static final String LTI_VERSION = "lti_version"; /** See the LTI specification */ public static final String RESOURCE_LINK_ID = "resource_link_id"; /** See the LTI specification */ public static final String RESOURCE_LINK_TITLE = "resource_link_title"; /** See the LTI specification */ public static final String RESOURCE_LINK_DESCRIPTION = "resource_link_description"; /** See the LTI specification */ public static final String USER_ID = "user_id"; /** See the LTI specification */ public static final String USER_IMAGE = "user_image"; /** See the LTI specification */ public static final String ROLES = "roles"; /** See the LTI specification */ public static final String GIVEN_NAME = "lis_person_name_given"; /** See the LTI specification */ public static final String FAMILY_NAME = "lis_person_name_family"; /** See the LTI specification */ public static final String FULL_NAME = "lis_person_name_full"; /** See the LTI specification */ public static final String EMAIL = "lis_person_contact_email_primary"; /** See the LTI specification */ public static final String CONTEXT_ID = "context_id"; /** See the LTI specification */ public static final String CONTEXT_TYPE = "context_type"; /** See the LTI specification */ public static final String CONTEXT_TITLE = "context_title"; /** See the LTI specification */ public static final String CONTEXT_LABEL = "context_label"; /** See the LTI specification */ public static final String LOCALE = "launch_presentation_locale"; /** See the LTI specification */ public static final String TARGET = "launch_presentation_document_target"; /** See the LTI specification */ public static final String WIDTH = "launch_presentation_width"; /** See the LTI specification */ public static final String HEIGHT = "launch_presentation_height"; /** See the LTI specification */ public static final String RETURN_URL = "launch_presentation_return_url"; /** See the LTI specification */ public static final String CONSUMER_GUID = "tool_consumer_instance_guid"; /** See the LTI specification */ public static final String CONSUMER_NAME = "tool_consumer_instance_name"; /** See the LTI specification */ public static final String CONSUMER_DESCRIPTION = "tool_consumer_instance_description"; /** See the LTI specification */ public static final String CONSUMER_URL = "tool_consumer_instance_url"; /** See the LTI specification */ public static final String CONSUMER_CONTACT = "tool_consumer_instance_contact_email"; public static final SortedSet<String> LTI_CONSTANTS; static { LTI_CONSTANTS = new TreeSet<String>(); LTI_CONSTANTS.add(LTI_MESSAGE_TYPE); LTI_CONSTANTS.add(LTI_VERSION); LTI_CONSTANTS.add(RESOURCE_LINK_ID); LTI_CONSTANTS.add(RESOURCE_LINK_TITLE); LTI_CONSTANTS.add(RESOURCE_LINK_DESCRIPTION); LTI_CONSTANTS.add(USER_ID); LTI_CONSTANTS.add(USER_IMAGE); LTI_CONSTANTS.add(ROLES); LTI_CONSTANTS.add(GIVEN_NAME); LTI_CONSTANTS.add(FAMILY_NAME); LTI_CONSTANTS.add(FULL_NAME); LTI_CONSTANTS.add(EMAIL); LTI_CONSTANTS.add(CONTEXT_ID); LTI_CONSTANTS.add(CONTEXT_TYPE); LTI_CONSTANTS.add(CONTEXT_TITLE); LTI_CONSTANTS.add(CONTEXT_LABEL); LTI_CONSTANTS.add(LOCALE); LTI_CONSTANTS.add(TARGET); LTI_CONSTANTS.add(WIDTH); LTI_CONSTANTS.add(HEIGHT); LTI_CONSTANTS.add(RETURN_URL); LTI_CONSTANTS.add(CONSUMER_GUID); LTI_CONSTANTS.add(CONSUMER_NAME); LTI_CONSTANTS.add(CONSUMER_DESCRIPTION); LTI_CONSTANTS.add(CONSUMER_URL); LTI_CONSTANTS.add(CONSUMER_CONTACT); } /** * {@inheritDoc} * * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, * javax.servlet.http.HttpServletResponse) */ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // Store the LTI data as a map in the session HttpSession session = req.getSession(false); session.setAttribute(SESSION_ATTRIBUTE_KEY, getLtiValuesAsMap(req)); // We must return a 200 for some oauth client libraries to accept this as a valid response // The URL of the LTI tool. If no specific tool is passed we use the test tool String toolReq = req.getParameter("custom_tool"); String toolUrl = "/ltisample/"; if (toolReq != null) { toolUrl = toolReq; if (!(toolUrl.indexOf("/") == 0)) { //if not supplied we assume this is a root path to the tool toolUrl = "/" + toolUrl; } } String customParams = getCustomParams(req); if (customParams != null) { toolUrl = toolUrl + "?" + customParams; } // Always set the session cookie resp.setHeader("Set-Cookie", "JSESSIONID=" + session.getId() + ";Path=/"); // The client can specify debug option by passing a value to test String testString = req.getParameter("custom_test"); boolean test = false; if (testString != null) { logger.debug("test: {}", req.getParameter("custom_test")); test = Boolean.valueOf(testString).booleanValue(); } //we need to add the custom params to the outgoing request // if in test mode display details where we go if (test) { resp.getWriter().write("<html><body>Welcome to matterhorn lti, you are going to " + toolUrl + "<br>"); resp.getWriter().write("<a href=\"" + toolUrl + "\">continue...</a></body></html>"); //TODO we should probably print the paramaters. } else { logger.debug(toolUrl); resp.sendRedirect(toolUrl); } } /** * Get a list of custom params to pass to the tool * @param req * @return */ protected String getCustomParams(HttpServletRequest req) { Map<String, String[]> paramMap = req.getParameterMap(); StringBuilder builder = new StringBuilder(); Set<String> entries = paramMap.keySet(); Iterator<String> iterator = entries.iterator(); while (iterator.hasNext()) { String key = iterator.next(); logger.debug("got key: " + key); if (key.indexOf(LTI_CUSTOM_PREFIX) >= 0) { String paramValue = req.getParameter(key); //we need to remove the prefix _custom String paramName = key.substring(LTI_CUSTOM_PREFIX.length()); logger.debug("Found custom var: " + paramName + ":" + paramValue); builder.append(paramName + "=" + paramValue + "&"); } } return builder.toString(); } /** * Builds a map of LTI parameters * * @param req * the LTI Launch HttpServletRequest * @return the map of LTI parameters to the values for this launch */ protected Map<String, String> getLtiValuesAsMap(HttpServletRequest req) { Map<String, String> ltiValues = new HashMap<String, String>(); for (String key : LTI_CONSTANTS) { String value = StringUtils.trimToNull(req.getParameter(key)); if (value != null) { ltiValues.put(key, value); } } return ltiValues; } /** * {@inheritDoc} * * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, * javax.servlet.http.HttpServletResponse) */ @SuppressWarnings("unchecked") @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(false); if (session == null) { resp.sendError(HttpServletResponse.SC_NOT_FOUND); // If there is no session, there is nothing to see here } else { Map<String, String> ltiAttributes = (Map<String, String>) session.getAttribute(SESSION_ATTRIBUTE_KEY); if (ltiAttributes == null) { ltiAttributes = new HashMap<String, String>(); } resp.setContentType("application/json"); JSONObject.writeJSONString(ltiAttributes, resp.getWriter()); } } }