com.portfolio.security.LTIv2Servlet.java Source code

Java tutorial

Introduction

Here is the source code for com.portfolio.security.LTIv2Servlet.java

Source

/* =======================================================
   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() {
    }

}