package org.imsglobal.json; import java.io.Reader; import java.io.ByteArrayInputStream; import java.net.URLDecoder; import java.util.List; import java.util.Map; import java.util.LinkedHashMap; import java.util.Iterator; import java.util.ArrayList; import java.util.Date; import java.util.Properties; import java.util.logging.Logger; import java.lang.IllegalArgumentException; import javax.servlet.http.HttpServletRequest; import net.oauth.OAuth; import net.oauth.OAuthMessage; import net.oauth.OAuthConsumer; import net.oauth.OAuthAccessor; import net.oauth.OAuthValidator; import net.oauth.SimpleOAuthValidator; import net.oauth.signature.OAuthSignatureMethod; import net.oauth.server.HttpRequestMessage; import net.oauth.server.OAuthServlet; import net.oauth.signature.OAuthSignatureMethod; import org.imsglobal.basiclti.Base64; import java.security.MessageDigest; import org.apache.commons.lang.StringEscapeUtils; public class IMSJSONRequest { private final static Logger Log = Logger.getLogger(IMSJSONRequest.class .getName()); public final static String STATUS = "status"; public final static String STATUS_CODE = "code"; public final static String STATUS_DESCRIPTION = "description"; public final static String CODE_MAJOR_SUCCESS = "success"; public final static String CODE_MAJOR_FAILURE = "failure"; public final static String CODE_MAJOR_UNSUPPORTED = "unsupported"; public String postBody = null; private String header = null; private String oauth_body_hash = null; private String oauth_consumer_key = null; public boolean valid = false; public String errorMessage = null; public String base_string = null; public String getOAuthConsumerKey() { return oauth_consumer_key; } public String getPostBody() { return postBody; } // Normal Constructor public IMSJSONRequest(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 IMSJSONRequest(HttpServletRequest request) { loadFromRequest(request); } // Constructor for testing... public IMSJSONRequest(String bodyString) { postBody = bodyString; } // Load but do not check the authentication public void loadFromRequest(HttpServletRequest request) { String contentType = request.getContentType(); if ( ! "application/json".equals(contentType) ) { errorMessage = "Content Type must be application/json"; Log.info(errorMessage+"\n"+contentType); return; } header = request.getHeader("Authorization"); System.out.println("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]); } } } if ( oauth_body_hash == null ) { errorMessage = "Did not find oauth_body_hash"; Log.info(errorMessage+"\n"+header); return; } System.out.println("OBH="+oauth_body_hash); final char[] buffer = new char[0x10000]; try { StringBuilder out = new StringBuilder(); Reader in = request.getReader(); int read; do { read = in.read(buffer, 0, buffer.length); if (read>0) { out.append(buffer, 0, read); } } while (read>=0); postBody = out.toString(); } catch(Exception e) { errorMessage = "Could not read message body:"+e.getMessage(); return; } try { MessageDigest md = MessageDigest.getInstance("SHA1"); md.update(postBody.getBytes()); byte[] output = Base64.encode(md.digest()); String hash = new String(output); 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; } valid = true; // So far we are valid } // Assumes data is all loaded public void validateRequest(String oauth_consumer_key, String oauth_secret, HttpServletRequest request) { valid = false; OAuthMessage oam = OAuthServlet.getMessage(request, null); 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 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; } public static Map getStatusUnsupported(String desc) { return getStatus(desc, CODE_MAJOR_UNSUPPORTED); } public static Map getStatusFailure(String desc) { return getStatus(desc, CODE_MAJOR_FAILURE); } public static Map getStatusSuccess(String desc) { return getStatus(desc, CODE_MAJOR_SUCCESS); } public static Map getStatus(String description, String major) { Map retval = new LinkedHashMap(); retval.put(STATUS_CODE,major); retval.put(STATUS_DESCRIPTION,description); return retval; } /** Unit Tests */ static final String inputTestData = "<?xml version = \"1.0\" encoding = \"UTF-8\"?>\n" + "<imsx_POXEnvelopeRequest xmlns = \"http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0\">\n" + "<imsx_POXHeader>\n" + "<imsx_POXRequestHeaderInfo>\n" + "<imsx_version>V1.0</imsx_version>\n" + "<imsx_messageIdentifier>999999123</imsx_messageIdentifier>\n" + "</imsx_POXRequestHeaderInfo>\n" + "</imsx_POXHeader>\n" + "<imsx_POXBody>\n" + "<replaceResultRequest>\n" + "<resultRecord>\n" + "<sourcedGUID>\n" + "<sourcedId>3124567</sourcedId>\n" + "</sourcedGUID>\n" + "<result>\n" + "<resultScore>\n" + "<language>en-us</language>\n" + "<textString>A</textString>\n" + "</resultScore>\n" + "</result>\n" + "</resultRecord>\n" + "</replaceResultRequest>\n" + "</imsx_POXBody>\n" + "</imsx_POXEnvelopeRequest>"; public static void runTest() { /* System.out.println("Runnig test."); IMSJSONRequest pox = new IMSJSONRequest(inputTestData); System.out.println("Version = "+pox.getHeaderVersion()); System.out.println("Operation = "+pox.getOperation()); Map<String,String> bodyMap = pox.getBodyMap(); String guid = bodyMap.get("/resultRecord/sourcedGUID/sourcedId"); System.out.println("guid="+guid); String grade = bodyMap.get("/resultRecord/result/resultScore/textString"); System.out.println("grade="+grade); String desc = "Message received and validated operation="+pox.getOperation()+ " guid="+guid+" grade="+grade; String output = pox.getResponseUnsupported(desc); System.out.println("---- Unsupported ----"); System.out.println(output); Properties props = new Properties(); props.setProperty("fred","zap"); props.setProperty("sam",IMSPOXRequest.MINOR_IDALLOC); System.out.println("---- Generate Log Error ----"); output = pox.getResponseFailure(desc,props); System.out.println("---- Failure ----"); System.out.println(output); Map<String, Object> theMap = new TreeMap<String, Object> (); theMap.put("/readMembershipResponse/membershipRecord/sourcedId", "123course456"); List<Map<String,String>> lm = new ArrayList<Map<String,String>>(); Map<String,String> mm = new TreeMap<String,String>(); mm.put("/personSourcedId","123user456"); mm.put("/role/roleType","Learner"); lm.add(mm); mm = new TreeMap<String,String>(); mm.put("/personSourcedId","789user123"); mm.put("/role/roleType","Instructor"); lm.add(mm); theMap.put("/readMembershipResponse/membershipRecord/membership/member", lm); String theXml = XMLMap.getXMLFragment(theMap, true); // System.out.println("th="+theXml); output = pox.getResponseSuccess(desc,theXml); System.out.println("---- Success String ----"); System.out.println(output); */ } /* roleType: Learner Instructor ContentDeveloper Member Manager Mentor Administrator TeachingAssistant fieldType: Boolean Integer Real String <readMembershipResponse xmlns="http://www.imsglobal.org/services/lis/mms2p0/wsdl11/sync/imsmms_v2p0"> <membershipRecord> <sourcedId>GUID.TYPE</sourcedId> <membership> <collectionSourcedId>GUID.TYPE</collectionSourcedId> <membershipIdType>MEMBERSHIPIDTYPE.TYPE</membershipIdType> <member> <personSourcedId>GUID.TYPE</personSourcedId> <role> <roleType>STRING</roleType> <subRole>STRING</subRole> <timeFrame> <begin>DATETIME</begin> <end>DATETIME</end> <restrict>BOOLEAN</restrict> <adminPeriod> <language>LANGUAGESET.TYPE</language> <textString>STRING</textString> </adminPeriod> </timeFrame> <status>STATUS.TYPE</status> <dateTime>DATETIME</dateTime> <dataSource>GUID.TYPE</dataSource> <recordInfo> <extensionField> <fieldName>STRING</fieldName> <fieldType>FIELDTYPE.TYPE</fieldType> <fieldValue>STRING</fieldValue> </extensionField> </recordInfo> <extension> <extensionField> <fieldName>STRING</fieldName> <fieldType>FIELDTYPE.TYPE</fieldType> <fieldValue>STRING</fieldValue> </extensionField> </extension> </role> </member> <creditHours>INTEGER</creditHours> <dataSource>GUID.TYPE</dataSource> </membership> </membershipRecord> </readMembershipResponse> */ }