org.imsglobal.pox.IMSPOXRequest Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of basiclti-util Show documentation
Show all versions of basiclti-util Show documentation
BasicLTI Utilities are a set of utility classes to aid in the development of BasicLTI consumers and
providers. They deal with much of the heavy lifting and make the process more opaque to the developer.
The newest version!
package org.imsglobal.pox;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.Reader;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import net.oauth.OAuthAccessor;
import net.oauth.OAuthConsumer;
import net.oauth.OAuthMessage;
import net.oauth.OAuthValidator;
import net.oauth.SimpleOAuthValidator;
import net.oauth.server.OAuthServlet;
import net.oauth.signature.OAuthSignatureMethod;
import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
import oauth.signpost.exception.OAuthException;
import oauth.signpost.http.HttpParameters;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.imsglobal.lti.XMLMap;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
public class IMSPOXRequest {
private final static Logger Log = Logger.getLogger(IMSPOXRequest.class .getName());
public final static String MAJOR_SUCCESS = "success";
public final static String MAJOR_FAILURE = "failure";
public final static String MAJOR_UNSUPPORTED = "unsupported";
public final static String MAJOR_PROCESSING = "processing";
public final static String [] validMajor = {
MAJOR_SUCCESS, MAJOR_FAILURE, MAJOR_UNSUPPORTED, MAJOR_PROCESSING };
public final static String SEVERITY_ERROR = "error";
public final static String SEVERITY_WARNING = "warning";
public final static String SEVERITY_STATUS = "status";
public final static String [] validSeverity = {
SEVERITY_ERROR, SEVERITY_WARNING, SEVERITY_STATUS };
public final static String MINOR_FULLSUCCESS ="fullsuccess";
public final static String MINOR_NOSOURCEDIDS = "nosourcedids";
public final static String MINOR_IDALLOC = "idalloc";
public final static String MINOR_OVERFLOWFAIL = "overflowfail";
public final static String MINOR_IDALLOCINUSEFAIL = "idallocinusefail";
public final static String MINOR_INVALIDDATAFAIL = "invaliddata";
public final static String MINOR_INCOMPLETEDATA = "incompletedata";
public final static String MINOR_PARTIALSTORAGE = "partialdatastorage";
public final static String MINOR_UNKNOWNOBJECT = "unknownobject";
public final static String MINOR_DELETEFAILURE = "deletefailure";
public final static String MINOR_TARGETREADFAILURE = "targetreadfailure";
public final static String MINOR_SAVEPOINTERROR = "savepointerror";
public final static String MINOR_SAVEPOINTSYNCERROR = "savepointsyncerror";
public final static String MINOR_UNKNOWNQUERY = "unknownquery";
public final static String MINOR_UNKNOWNVOCAB = "unknownvocab";
public final static String MINOR_TARGETISBUSY = "targetisbusy";
public final static String MINOR_UNKNOWNEXTENSION = "unknownextension";
public final static String MINOR_UNAUTHORIZEDREQUEST = "unauthorizedrequest";
public final static String MINOR_LINKFAILURE = "linkfailure";
public final static String MINOR_UNSUPPORTED = "unsupported";
public final static String [] validMinor = {
MINOR_FULLSUCCESS, MINOR_NOSOURCEDIDS, MINOR_IDALLOC, MINOR_OVERFLOWFAIL,
MINOR_IDALLOCINUSEFAIL, MINOR_INVALIDDATAFAIL, MINOR_INCOMPLETEDATA,
MINOR_PARTIALSTORAGE, MINOR_UNKNOWNOBJECT, MINOR_DELETEFAILURE,
MINOR_TARGETREADFAILURE, MINOR_SAVEPOINTERROR, MINOR_SAVEPOINTSYNCERROR,
MINOR_UNKNOWNQUERY, MINOR_UNKNOWNVOCAB, MINOR_TARGETISBUSY,
MINOR_UNKNOWNEXTENSION, MINOR_UNAUTHORIZEDREQUEST, MINOR_LINKFAILURE,
MINOR_UNSUPPORTED
} ;
public Document postDom = null;
public Element bodyElement = null;
public Element headerElement = null;
public String postBody = null;
private String header = null;
private String oauth_body_hash = null;
private String oauth_consumer_key = null;
public boolean valid = false;
private String operation = null;
public String errorMessage = null;
public String base_string = null;
private Map bodyMap = null;
private Map headerMap = null;
public String getOperation()
{
return operation;
}
public String getOAuthConsumerKey()
{
return oauth_consumer_key;
}
public String getHeaderVersion()
{
return getHeaderItem("/imsx_version");
}
public String getHeaderMessageIdentifier()
{
return getHeaderItem("/imsx_messageIdentifier");
}
public String getHeaderItem(String path)
{
if ( getHeaderMap() == null ) return null;
return headerMap.get(path);
}
public Map getHeaderMap()
{
if ( headerMap != null ) return headerMap;
if ( headerElement == null ) return null;
headerMap = XMLMap.getMap(headerElement);
return headerMap;
}
public Map getBodyMap()
{
if ( bodyMap != null ) return bodyMap;
if ( bodyElement == null ) return null;
bodyMap = XMLMap.getMap(bodyElement);
return bodyMap;
}
public String getPostBody()
{
return postBody;
}
// Normal Constructor
public IMSPOXRequest(String oauth_consumer_key, String oauth_secret, HttpServletRequest request)
{
loadFromRequest(request);
if ( ! valid ) return;
validateRequest(oauth_consumer_key, oauth_secret, request);
}
// Constructor for delayed validation
public IMSPOXRequest(HttpServletRequest request)
{
loadFromRequest(request);
}
// Constructor for testing...
public IMSPOXRequest(String bodyString)
{
postBody = bodyString;
parsePostBody();
}
// Load but do not check the authentication
public void loadFromRequest(HttpServletRequest request)
{
String contentType = request.getContentType();
if ( ! "application/xml".equals(contentType) ) {
errorMessage = "Content Type must be application/xml";
Log.info(errorMessage+"\n"+contentType);
return;
}
setAuthHeader(request.getHeader("Authorization"));
if ( oauth_body_hash == null ) {
errorMessage = "Did not find oauth_body_hash";
Log.info(errorMessage+"\n"+header);
return;
}
try {
Reader in = request.getReader();
postBody = readPostBody(in);
} catch(Exception e) {
errorMessage = "Could not read message body:"+e.getMessage();
return;
}
validatePostBody();
if (errorMessage != null) return;
parsePostBody();
}
@SuppressWarnings("deprecation")
public void setAuthHeader(String header) {
this.header = header;
oauth_body_hash = null;
if ( header != null ) {
if (header.startsWith("OAuth ")) header = header.substring(5);
String [] parms = header.split(",");
for ( String parm : parms ) {
parm = parm.trim();
if ( parm.startsWith("oauth_body_hash=") ) {
String [] pieces = parm.split("\"");
oauth_body_hash = URLDecoder.decode(pieces[1]);
}
if ( parm.startsWith("oauth_consumer_key=") ) {
String [] pieces = parm.split("\"");
oauth_consumer_key = URLDecoder.decode(pieces[1]);
}
}
}
}
public static String readPostBody(Reader in) throws IOException {
// System.out.println("OBH="+oauth_body_hash);
final char[] buffer = new char[0x10000];
StringBuilder out = new StringBuilder();
int read;
do {
read = in.read(buffer, 0, buffer.length);
if (read > 0) {
out.append(buffer, 0, read);
}
} while (read >= 0);
return out.toString();
}
public static String getBodyHash(String postBody) throws GeneralSecurityException {
MessageDigest md = MessageDigest.getInstance("SHA1");
md.update(postBody.getBytes());
byte[] output = Base64.encodeBase64(md.digest());
return new String(output);
}
public void validatePostBody() {
try {
String hash = getBodyHash(postBody);
// System.out.println("HASH="+hash);
if ( ! hash.equals(oauth_body_hash) ) {
errorMessage = "Body hash does not match header";
return;
}
} catch (Exception e) {
errorMessage = "Could not compute body hash";
return;
}
}
public void parsePostBody()
{
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
DocumentBuilder db = dbf.newDocumentBuilder();
postDom = db.parse(new ByteArrayInputStream(postBody.getBytes()));
}catch(Exception e) {
errorMessage = "Could not parse XML: "+e.getMessage();
return;
}
try {
XPath xpath = XPathFactory.newInstance().newXPath();
XPathExpression expr = xpath.compile("/imsx_POXEnvelopeRequest/imsx_POXBody/*");
Object result = expr.evaluate(postDom, XPathConstants.NODESET);
NodeList nodes = (NodeList) result;
bodyElement = (Element) nodes.item(0);
operation = bodyElement.getNodeName();
expr = xpath.compile("/imsx_POXEnvelopeRequest/imsx_POXHeader/*");
result = expr.evaluate(postDom, XPathConstants.NODESET);
nodes = (NodeList) result;
headerElement = (Element) nodes.item(0);
}catch(javax.xml.xpath.XPathExpressionException e) {
errorMessage = "Could not parse XPATH: "+e.getMessage();
return;
}catch(Exception e) {
errorMessage = "Could not parse input XML: "+e.getMessage();
return;
}
if ( operation == null || bodyElement == null ) {
errorMessage = "Could not find operation";
return;
}
valid = true;
}
// Assumes data is all loaded
public void validateRequest(String oauth_consumer_key, String oauth_secret, HttpServletRequest request)
{
validateRequest(oauth_consumer_key, oauth_secret, request, null) ;
}
public void validateRequest(String oauth_consumer_key, String oauth_secret, HttpServletRequest request, String URL)
{
valid = false;
OAuthMessage oam = OAuthServlet.getMessage(request, URL);
OAuthValidator oav = new SimpleOAuthValidator();
OAuthConsumer cons = new OAuthConsumer("about:blank#OAuth+CallBack+NotUsed",
oauth_consumer_key, oauth_secret, null);
OAuthAccessor acc = new OAuthAccessor(cons);
try {
base_string = OAuthSignatureMethod.getBaseString(oam);
} catch (Exception e) {
base_string = null;
}
try {
oav.validateMessage(oam,acc);
} catch(Exception e) {
errorMessage = "Launch fails OAuth validation: "+e.getMessage();
return;
}
valid = true;
}
public static String fetchTag(org.w3c.dom.Element element, String tag)
{
try {
org.w3c.dom.NodeList elements = element.getElementsByTagName(tag);
int numElements = elements.getLength();
if (numElements > 0) {
org.w3c.dom.Element e = (org.w3c.dom.Element)elements.item(0);
if (e.hasChildNodes()) {
return e.getFirstChild().getNodeValue();
}
}
} catch (Throwable t) {
Log.warning(t.getMessage());
// t.printStackTrace();
}
return null;
}
public boolean inArray(final String [] theArray, final String theString)
{
if ( theString == null ) return false;
for ( String str : theArray ) {
if ( theString.equals(str) ) return true;
}
return false;
}
static final String fatalMessage =
"\n" +
"\n" +
" \n" +
" \n" +
" V1.0 \n" +
" %s \n" +
" \n" +
" failure \n" +
" error \n" +
" %s \n" +
" %s " +
" \n" +
" \n" +
" \n" +
" \n" +
" ";
public static String getFatalResponse(String description)
{
return getFatalResponse(description, "unknown");
}
public static String getFatalResponse(String description, String message_id)
{
Date dt = new Date();
String messageId = ""+dt.getTime();
return String.format(fatalMessage,
StringEscapeUtils.escapeXml(messageId),
StringEscapeUtils.escapeXml(description),
StringEscapeUtils.escapeXml(message_id));
}
static final String responseMessage =
"\n" +
"\n" +
" \n" +
" \n" +
" V1.0 \n" +
" %s \n" +
" \n" +
" %s \n" +
" %s \n" +
" %s \n" +
" %s \n" +
" %s " +
"%s\n"+
" \n" +
" \n" +
" \n" +
" \n" +
"%s%s"+
" \n" +
" ";
public String getResponseUnsupported(String desc)
{
return getResponse(desc, MAJOR_UNSUPPORTED, null, null, null, null);
}
public String getResponseFailure(String desc, Properties minor)
{
return getResponse(desc, null, null, null, minor, null);
}
public String getResponseFailure(String desc, Properties minor, String bodyString)
{
return getResponse(desc, null, null, null, minor, bodyString);
}
public String getResponseSuccess(String desc, String bodyString)
{
return getResponse(desc, MAJOR_SUCCESS, null, null, null, bodyString);
}
public String getResponse(String description, String major, String severity,
String messageId, Properties minor, String bodyString)
{
StringBuffer internalError = new StringBuffer();
if ( major == null ) major = MAJOR_FAILURE;
if ( severity == null && MAJOR_PROCESSING.equals(major) ) severity = SEVERITY_STATUS;
if ( severity == null && MAJOR_SUCCESS.equals(major) ) severity = SEVERITY_STATUS;
if ( severity == null ) severity = SEVERITY_ERROR;
if ( messageId == null ) {
Date dt = new Date();
messageId = ""+dt.getTime();
}
StringBuffer sb = new StringBuffer();
if ( minor != null && minor.size() > 0 ) {
for(Object okey : minor.keySet() ) {
String key = (String) okey;
String value = minor.getProperty(key);
if ( key == null || value == null ) continue;
if ( !inArray(validMinor, value) ) {
if ( internalError.length() > 0 ) sb.append(", ");
internalError.append("Invalid imsx_codeMinorFieldValue="+major);
continue;
}
if ( sb.length() == 0 ) sb.append("\n \n");
sb.append(" \n ");
sb.append(key);
sb.append(" \n ");
sb.append(StringEscapeUtils.escapeXml(value));
sb.append(" \n \n");
}
if ( sb.length() > 0 ) sb.append(" ");
}
String minorString = sb.toString();
if ( ! inArray(validMajor, major) ) {
if ( internalError.length() > 0 ) sb.append(", ");
internalError.append("Invalid imsx_codeMajor="+major);
}
if ( ! inArray(validSeverity, severity) ) {
if ( internalError.length() > 0 ) sb.append(", ");
internalError.append("Invalid imsx_severity="+major);
}
if ( internalError.length() > 0 ) {
description = description + " (Internal error: " + internalError.toString() + ")";
Log.warning(internalError.toString());
}
if ( bodyString == null ) bodyString = "";
// Trim off XML header
if ( bodyString.startsWith(" 0 ) bodyString = bodyString.substring(pos);
}
bodyString = bodyString.trim();
String newLine = "";
if ( bodyString.length() > 0 ) newLine = "\n";
return String.format(responseMessage,
StringEscapeUtils.escapeXml(messageId),
StringEscapeUtils.escapeXml(major),
StringEscapeUtils.escapeXml(severity),
StringEscapeUtils.escapeXml(description),
StringEscapeUtils.escapeXml(getHeaderMessageIdentifier()),
StringEscapeUtils.escapeXml(operation),
StringEscapeUtils.escapeXml(minorString),
bodyString, newLine);
}
/**
* A template string for creating ReplaceResult messages.
*
* Similar to {@link #replaceResultMessage}, except has support for messageIdentifier
*
* Use like:
*
* String.format(
* ReplaceResultMessageTemplate,
* messageId,
* sourcedId,
* resultScore,
* resultDataXml
* )
*
*
*
*/
public static final String ReplaceResultMessageTemplate =
""+
""+
" "+
" "+
" V1.0 "+
" %s "+
" "+
" "+
" "+
" "+
" "+
" "+
" %s "+
" "+
" "+
" "+
" en "+
" %s "+
" "+
" %s"+
" "+
" "+
" "+
" "+
" ";
/**
* @deprecated use {@link #ReplaceResultMessageTemplate} instead.
*/
@Deprecated
public static final String replaceResultMessage =
""+
""+
" "+
" "+
" V1.0 "+
" "+
" "+
" "+
" "+
" "+
" "+
" %s "+
" "+
" "+
" "+
" en "+
" %s "+
" "+
" %s"+
" "+
" "+
" "+
" "+
" ";
static final String resultDataText = "%s ";
static final String resultDataUrl = "%s ";
public static void sendReplaceResult(String url, String key, String secret, String sourcedid, String score) throws IOException, OAuthException, GeneralSecurityException {
sendReplaceResult(url, key, secret, sourcedid, score, null);
}
public static void sendReplaceResult(String url, String key, String secret, String sourcedid, String score, String resultData) throws IOException, OAuthException, GeneralSecurityException {
sendReplaceResult(url, key, secret, sourcedid, score, resultData, false);
}
public static void sendReplaceResult(String url, String key, String secret, String sourcedid, String score, String resultData, Boolean isUrl) throws IOException, OAuthException, GeneralSecurityException {
HttpPost request = buildReplaceResult(url, key, secret, sourcedid, score, resultData, isUrl);
DefaultHttpClient client = new DefaultHttpClient();
HttpResponse response = client.execute(request);
if (response.getStatusLine().getStatusCode() >= 400) {
throw new HttpResponseException(response.getStatusLine().getStatusCode(),
response.getStatusLine().getReasonPhrase());
}
}
public static HttpPost buildReplaceResult(String url, String key, String secret, String sourcedid, String score, String resultData, Boolean isUrl) throws IOException, OAuthException, GeneralSecurityException {
return buildReplaceResult(url, key, secret, sourcedid, score, resultData, isUrl, null);
}
public static HttpPost buildReplaceResult(String url, String key, String secret, String sourcedid,
String score, String resultData, Boolean isUrl, String messageId) throws IOException, OAuthException, GeneralSecurityException
{
String dataXml = "";
if (resultData != null) {
String format = isUrl ? resultDataUrl : resultDataText;
dataXml = String.format(format, StringEscapeUtils.escapeXml(resultData));
}
String messageIdentifier = StringUtils.isBlank(messageId) ? String.valueOf(new Date().getTime()) : messageId;
String xml = String.format(ReplaceResultMessageTemplate,
StringEscapeUtils.escapeXml(messageIdentifier),
StringEscapeUtils.escapeXml(sourcedid),
StringEscapeUtils.escapeXml(score),
dataXml);
HttpParameters parameters = new HttpParameters();
String hash = getBodyHash(xml);
parameters.put("oauth_body_hash", URLEncoder.encode(hash, "UTF-8"));
CommonsHttpOAuthConsumer signer = new CommonsHttpOAuthConsumer(key, secret);
HttpPost request = new HttpPost(url);
request.setHeader("Content-Type", "application/xml");
request.setEntity(new StringEntity(xml, "UTF-8"));
signer.setAdditionalParameters(parameters);
signer.sign(request);
return request;
}
/*
roleType:
Learner
Instructor
ContentDeveloper
Member
Manager
Mentor
Administrator
TeachingAssistant
fieldType:
Boolean
Integer
Real
String
GUID.TYPE
GUID.TYPE
MEMBERSHIPIDTYPE.TYPE
GUID.TYPE
STRING
STRING
DATETIME
DATETIME
BOOLEAN
LANGUAGESET.TYPE
STRING
STATUS.TYPE
DATETIME
GUID.TYPE
STRING
FIELDTYPE.TYPE
STRING
STRING
FIELDTYPE.TYPE
STRING
INTEGER
GUID.TYPE
*/
}