Java tutorial
/* ======================================================= Copyright 2014 - ePortfolium - Licensed 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://www.osedu.org/licenses/ECL-2.0 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 com.portfolio.security; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Date; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import net.oauth.OAuthAccessor; import net.oauth.OAuthConsumer; import net.oauth.OAuthMessage; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.imsglobal.basiclti.Base64; import org.imsglobal.basiclti.BasicLTIConstants; import org.imsglobal.json.IMSJSONRequest; import org.imsglobal.lti2.LTI2Constants; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; public class LTIv2Servlet extends HttpServlet { private static final long serialVersionUID = -2442074091303775050L; private static Log M_log = LogFactory.getLog(LTIv2Servlet.class); @SuppressWarnings("unused") private static final String EMPTY_JSON_OBJECT = "{\n}\n"; @Override public void init(ServletConfig config) throws ServletException { super.init(config); } private void outTraceFormattedMessage(StringBuffer outTrace, String msg) { outTrace.append("\n" + new Date() + " DEBUG " + msg); } @Override protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { StringBuffer outTrace = new StringBuffer(); String logFName = null; HttpSession session = request.getSession(true); String ppath = session.getServletContext().getRealPath("/"); String outsideDir = ppath.substring(0, ppath.lastIndexOf("/")) + "_files/"; ServletContext application = getServletConfig().getServletContext(); //super.doPost(request, response); logFName = outsideDir + "logs/logLTI2.txt"; outTraceFormattedMessage(outTrace, "doPost() - " + logFName); String toolProxyPath = outsideDir + "tool_proxy.txt"; try { // wadbackend.WadUtilities.setApplicationAttributes(application, session); doRequest(request, response, session, application, toolProxyPath, outTrace); } catch (Exception e) { String ipAddress = request.getRemoteAddr(); String uri = request.getRequestURI(); M_log.warn("General LTI2 Failure URI=" + uri + " IP=" + ipAddress); e.printStackTrace(); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); doErrorJSON(request, response, null, "General failure", e); } finally { outTraceFormattedMessage(outTrace, "In finally()"); if (LTIServletUtils.isTrace(application)) { outTraceFormattedMessage(outTrace, "In finally() with trace"); // wadbackend.WadUtilities.appendlogfile(logFName, "POSTlti:" + outTrace.toString()); } } } @SuppressWarnings("unused") protected void doRequest(HttpServletRequest request, HttpServletResponse response, HttpSession session, ServletContext application, String toolProxyPath, StringBuffer outTrace) throws ServletException, IOException { outTraceFormattedMessage(outTrace, "getServiceURL=" + getServiceURL(request)); String ipAddress = request.getRemoteAddr(); outTraceFormattedMessage(outTrace, "LTI Service request from IP=" + ipAddress); String rpi = request.getPathInfo(); String uri = request.getRequestURI(); String[] parts = uri.split("/"); if (parts.length < 4) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); doErrorJSON(request, response, null, "Incorrect url format", null); return; } Map<String, Object> payload = LTIServletUtils.processRequest(request, outTrace); String url = getServiceURL(request); String controller = parts[3]; if ("register".equals(controller)) { payload.put("base_url", url); payload.put("launch_url", url + "register"); doRegister(response, payload, application, toolProxyPath, outTrace); return; } else if ("launch".equals(controller)) { doLaunch(request, response, session, payload, application, outTrace); return; } // Check if json request if valid IMSJSONRequest jsonRequest = new IMSJSONRequest(request); if (jsonRequest.valid) { outTraceFormattedMessage(outTrace, jsonRequest.getPostBody()); } response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); M_log.warn("Unknown request=" + uri); doErrorJSON(request, response, null, "Unknown request=" + uri, null); } protected void doRegister(HttpServletResponse response, Map<String, Object> payload, ServletContext application, String toolProxyPath, StringBuffer outTrace) { String launch_url = (String) payload.get("launch_url"); response.setContentType("text/html"); outTraceFormattedMessage(outTrace, "doRegister() - launch_url: " + launch_url); outTraceFormattedMessage(outTrace, "payload: " + payload); String key = null; String passwd = null; if (BasicLTIConstants.LTI_MESSAGE_TYPE_TOOLPROXYREGISTRATIONREQUEST .equals(payload.get(BasicLTIConstants.LTI_MESSAGE_TYPE))) { key = (String) payload.get(LTI2Constants.REG_KEY); passwd = (String) payload.get(LTI2Constants.REG_PASSWORD); } else if (BasicLTIConstants.LTI_MESSAGE_TYPE_TOOLPROXY_RE_REGISTRATIONREQUEST .equals(payload.get(BasicLTIConstants.LTI_MESSAGE_TYPE))) { key = (String) payload.get(LTIServletUtils.OAUTH_CONSUMER_KEY); final String configPrefix = "basiclti.provider." + key + "."; passwd = (String) application.getAttribute(configPrefix + "secret"); } else { //TODO BOOM outTraceFormattedMessage(outTrace, "BOOM"); } String returnUrl = (String) payload.get(BasicLTIConstants.LAUNCH_PRESENTATION_RETURN_URL); String tcProfileUrl = (String) payload.get(LTI2Constants.TC_PROFILE_URL); //Lookup tc profile if (tcProfileUrl != null && !"".equals(tcProfileUrl)) { InputStream is = null; try { URL url = new URL(tcProfileUrl); is = url.openStream(); JSONParser parser = new JSONParser(); JSONObject obj = (JSONObject) parser.parse(new InputStreamReader(is)); // is.close(); outTraceFormattedMessage(outTrace, obj.toJSONString()); JSONArray services = (JSONArray) obj.get("service_offered"); String regUrl = null; for (int i = 0; i < services.size(); i++) { JSONObject service = (JSONObject) services.get(i); JSONArray formats = (JSONArray) service.get("format"); if (formats.contains("application/vnd.ims.lti.v2.toolproxy+json")) { regUrl = (String) service.get("endpoint"); outTraceFormattedMessage(outTrace, "RegEndpoint: " + regUrl); } } if (regUrl == null) { //TODO BOOM throw new RuntimeException("Need an endpoint"); } JSONObject toolProxy = getToolProxy(toolProxyPath); //TODO do some replacement on stock values that need specifics from us here // Tweak the stock profile toolProxy.put("tool_consumer_profile", tcProfileUrl); //LTI2Constants. // BasicLTIConstants. // // Re-register JSONObject toolProfile = (JSONObject) toolProxy.get("tool_profile"); JSONArray messages = (JSONArray) toolProfile.get("message"); JSONObject message = (JSONObject) messages.get(0); message.put("path", launch_url); String baseUrl = (String) payload.get("base_url"); JSONObject pi = (JSONObject) toolProfile.get("product_instance"); JSONObject pInfo = (JSONObject) pi.get("product_info"); JSONObject pFamily = (JSONObject) pInfo.get("product_family"); JSONObject vendor = (JSONObject) pFamily.get("vendor"); vendor.put("website", baseUrl); // vendor.put("timestamp", new Date().toString()); // $tp_profile->tool_profile->product_instance->product_info->product_family->vendor->website = $cur_base; // $tp_profile->tool_profile->product_instance->product_info->product_family->vendor->timestamp = "2013-07-13T09:08:16-04:00"; // // // I want this *not* to be unique per instance // $tp_profile->tool_profile->product_instance->guid = "urn:sakaiproject:unit-test"; // // $tp_profile->tool_profile->product_instance->service_provider->guid = "http://www.sakaiproject.org/"; // // // Launch Request // $tp_profile->tool_profile->resource_handler[0]->message[0]->path = "tool.php"; // $tp_profile->tool_profile->resource_handler[0]->resource_type->code = "sakai-api-test-01"; // $tp_profile->tool_profile->base_url_choice[0]->secure_base_url = $cur_base; // $tp_profile->tool_profile->base_url_choice[0]->default_base_url = $cur_base; JSONObject choice = (JSONObject) ((JSONArray) toolProfile.get("base_url_choice")).get(0); choice.put("secure_base_url", baseUrl); choice.put("default_base_url", baseUrl); JSONObject secContract = (JSONObject) toolProxy.get("security_contract"); secContract.put("shared_secret", passwd); JSONArray toolServices = (JSONArray) secContract.get("tool_service"); JSONObject service = (JSONObject) toolServices.get(0); service.put("service", regUrl); outTraceFormattedMessage(outTrace, "ToolProxyJSON: " + toolProxy.toJSONString()); /// From the Implementation Guid Version 2.0 Final (http://www.imsglobal.org/lti/ltiv2p0/ltiIMGv2p0.html) /// Section 10.1 /// Get data JSONObject dataService = getData(tcProfileUrl); /// find endpoint with format: application/vnd.ims.lti.v2.toolproxy+json WITH POST action JSONArray offered = (JSONArray) dataService.get("service_offered"); String registerAddress = ""; for (int i = 0; i < offered.size(); ++i) { JSONObject offer = (JSONObject) offered.get(i); JSONArray offerFormat = (JSONArray) offer.get("format"); String format = (String) offerFormat.get(0); JSONArray offerAction = (JSONArray) offer.get("action"); String action = (String) offerAction.get(0); if ("application/vnd.ims.lti.v2.toolproxy+json".equals(format) && "POST".equals(action)) { registerAddress = (String) offer.get("endpoint"); break; } } /// FIXME: Sakai return server name as "localhost", could be my configuration String[] serverAddr = tcProfileUrl.split("/"); String addr = serverAddr[2]; registerAddress = registerAddress.substring(registerAddress.indexOf("/", 8)); registerAddress = "http://" + addr + registerAddress; /// Send POST to specified URL as signed request with given values int responseCode = postData(registerAddress, toolProxy.toJSONString(), key, passwd); if (responseCode != HttpServletResponse.SC_CREATED) { //TODO BOOM! throw new RuntimeException("Bad response code. Got " + responseCode + " but expected " + HttpServletResponse.SC_CREATED); } } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { if (is != null) is.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } String output = "<a href='" + returnUrl + "'>Continue to launch presentation url</a>"; try { PrintWriter out = response.getWriter(); out.println(output); } catch (Exception e) { e.printStackTrace(); } } private int postData(String urlStr, String jsonData, String oauth_consumer_key, String oauth_consumer_secret) throws IOException { URL url = new URL(urlStr); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setDoOutput(true); connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/vnd.ims.lti.v2.toolproxy+json"); connection.setRequestProperty("Content-Length", String.valueOf(jsonData.length())); Map<String, String> postProp = new HashMap<String, String>(); try { MessageDigest md = MessageDigest.getInstance("SHA1"); md.update(jsonData.getBytes()); byte[] output = Base64.encode(md.digest()); String hash = new String(output); postProp.put("oauth_body_hash", hash); } catch (NoSuchAlgorithmException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } OAuthMessage oam = new OAuthMessage(OAuthMessage.POST, urlStr, postProp.entrySet()); OAuthConsumer cons = new OAuthConsumer("about:blank", oauth_consumer_key, oauth_consumer_secret, null); OAuthAccessor acc = new OAuthAccessor(cons); try { oam.addRequiredParameters(acc); connection.setRequestProperty("Authorization", oam.getAuthorizationHeader(null)); oam.sign(acc); } catch (net.oauth.OAuthException e) { throw new Error(e); } catch (java.io.IOException e) { throw new Error(e); } catch (java.net.URISyntaxException e) { throw new Error(e); } // Write data OutputStream os = connection.getOutputStream(); os.write(jsonData.getBytes()); // Read response int responseCode = connection.getResponseCode(); // Close streams os.close(); return responseCode; } private JSONObject getData(String urlStr) throws IOException, ParseException { URL url = new URL(urlStr); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setDoOutput(true); connection.setRequestMethod("GET"); connection.setRequestProperty("Content-Type", "application/vnd.ims.lti.v2.toolproxy+json"); connection.connect(); BufferedReader rd = new BufferedReader(new InputStreamReader(connection.getInputStream())); StringBuilder sb = new StringBuilder(); String line = ""; while ((line = rd.readLine()) != null) sb.append(line); JSONObject tp = (JSONObject) new JSONParser().parse(sb.toString()); return tp; } @SuppressWarnings("unused") protected void doLaunch(HttpServletRequest request, HttpServletResponse response, HttpSession session, Map<String, Object> payload, ServletContext application, StringBuffer outTrace) throws ServletException, IOException { //TODO Figure out how to validate v1 and v2 params // LTIServletUtils.validateParams(payload, application, outTrace); LTIServletUtils.oauthValidate(request, payload, application); outTraceFormattedMessage(outTrace, "doLaunch()"); LTIServletUtils.handleLaunch(payload, application, response, session, outTrace); } private JSONObject getToolProxy(String filePath) { try { File f = new File(filePath); if (!f.exists()) { f.mkdirs(); f.createNewFile(); } FileInputStream fis = new FileInputStream(filePath); BufferedReader br = new BufferedReader(new InputStreamReader(fis, "UTF-8")); JSONObject tp = (JSONObject) new JSONParser().parse(br); return tp; } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } private String getServiceURL(HttpServletRequest request) { String scheme = request.getScheme(); // http String serverName = request.getServerName(); // localhost int serverPort = request.getServerPort(); // 80 String contextPath = request.getContextPath(); // /imsblis String servletPath = request.getServletPath(); // /ltitest String url = scheme + "://" + serverName + ":" + serverPort + contextPath + servletPath + "/"; return url; } /* IMS JSON version of Errors */ private void doErrorJSON(HttpServletRequest request, HttpServletResponse response, IMSJSONRequest json, String message, Exception e) throws java.io.IOException { if (e != null) { M_log.error(e.getLocalizedMessage(), e); } M_log.info(message); String output = IMSJSONRequest.doErrorJSON(request, response, json, message, e); System.out.println(output); } @Override public void destroy() { } }