public String getBodyHash() throws OAuthException { byte[] output; try { MessageDigest md = MessageDigest.getInstance("SHA1"); md.update(bodyAsByteArray); output = OAuthSignatureMethod.base64Encode(md.digest()).getBytes(); } catch (Exception e) { throw new OAuthException("Could not compute body hash: " + e.getMessage()); } return new String(output); }
@SuppressWarnings("unchecked") protected void doPostForm(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String ipAddress = request.getRemoteAddr(); M_log.debug("Basic LTI Service request from IP=" + ipAddress); String allowOutcomes = ServerConfigurationService.getString( SakaiBLTIUtil.BASICLTI_OUTCOMES_ENABLED, SakaiBLTIUtil.BASICLTI_OUTCOMES_ENABLED_DEFAULT); if (!"true".equals(allowOutcomes)) allowOutcomes = null; String allowSettings = ServerConfigurationService.getString( SakaiBLTIUtil.BASICLTI_SETTINGS_ENABLED, SakaiBLTIUtil.BASICLTI_SETTINGS_ENABLED_DEFAULT); if (!"true".equals(allowSettings)) allowSettings = null; String allowRoster = ServerConfigurationService.getString( SakaiBLTIUtil.BASICLTI_ROSTER_ENABLED, SakaiBLTIUtil.BASICLTI_ROSTER_ENABLED_DEFAULT); if (!"true".equals(allowRoster)) allowRoster = null; if (allowOutcomes == null && allowSettings == null && allowRoster == null) { M_log.warn("LTI Services are disabled IP=" + ipAddress); response.setStatus(HttpServletResponse.SC_FORBIDDEN); return; } // Lets return an XML Response Map<String, Object> theMap = new TreeMap<String, Object>(); Map<String, String[]> params = (Map<String, String[]>) request.getParameterMap(); for (Map.Entry<String, String[]> param : params.entrySet()) { M_log.debug(param.getKey() + ":" + param.getValue()[0]); } // check lti_message_type String lti_message_type = request.getParameter(BasicLTIConstants.LTI_MESSAGE_TYPE); theMap.put("/message_response/lti_message_type", lti_message_type); String sourcedid = null; String message_type = null; if (BasicLTIUtil.equals(lti_message_type, "basic-lis-replaceresult") || BasicLTIUtil.equals(lti_message_type, "basic-lis-createresult") || BasicLTIUtil.equals(lti_message_type, "basic-lis-updateresult") || BasicLTIUtil.equals(lti_message_type, "basic-lis-deleteresult") || BasicLTIUtil.equals(lti_message_type, "basic-lis-readresult")) { sourcedid = request.getParameter("sourcedid"); if (allowOutcomes != null) message_type = "basicoutcome"; } else if (BasicLTIUtil.equals(lti_message_type, "basic-lti-loadsetting") || BasicLTIUtil.equals(lti_message_type, "basic-lti-savesetting") || BasicLTIUtil.equals(lti_message_type, "basic-lti-deletesetting")) { sourcedid = request.getParameter("id"); if (allowSettings != null) message_type = "toolsetting"; } else if (BasicLTIUtil.equals(lti_message_type, "basic-lis-readmembershipsforcontext")) { sourcedid = request.getParameter("id"); if (allowRoster != null) message_type = "roster"; } else { doError( request, response, theMap, "outcomes.invalid", "lti_message_type=" + lti_message_type, null); return; } // If we have not gotten one of our allowed message types, stop now if (message_type == null) { doError( request, response, theMap, "outcomes.invalid", "lti_message_type=" + lti_message_type, null); return; } // Perform the Outcomee first because we use the SakaiBLTIUtil code for this if ("basicoutcome".equals(message_type)) { processOutcome(request, response, lti_message_type, sourcedid, theMap); return; } // No point continuing without a sourcedid if (BasicLTIUtil.isBlank(sourcedid)) { doError(request, response, theMap, "outcomes.missing", "sourcedid", null); return; } String lti_version = request.getParameter(BasicLTIConstants.LTI_VERSION); if (!BasicLTIUtil.equals(lti_version, "LTI-1p0")) { doError(request, response, theMap, "outcomes.invalid", "lti_version=" + lti_version, null); return; } String oauth_consumer_key = request.getParameter("oauth_consumer_key"); if (BasicLTIUtil.isBlank(oauth_consumer_key)) { doError(request, response, theMap, "outcomes.missing", "oauth_consumer_key", null); return; } // Truncate this to the maximum length to insure no cruft at the end if (sourcedid.length() > 2048) sourcedid = sourcedid.substring(0, 2048); // Attempt to parse the sourcedid, any failure is fatal String placement_id = null; String signature = null; String user_id = null; try { int pos = sourcedid.indexOf(":::"); if (pos > 0) { signature = sourcedid.substring(0, pos); String dec2 = sourcedid.substring(pos + 3); pos = dec2.indexOf(":::"); user_id = dec2.substring(0, pos); placement_id = dec2.substring(pos + 3); } } catch (Exception e) { // Log some detail for ourselves M_log.warn( "Unable to decrypt result_sourcedid IP=" + ipAddress + " Error=" + e.getMessage(), e); signature = null; placement_id = null; user_id = null; } // Send a more generic message back to the caller if (placement_id == null || user_id == null) { doError(request, response, theMap, "outcomes.sourcedid", "sourcedid", null); return; } M_log.debug("signature=" + signature); M_log.debug("user_id=" + user_id); M_log.debug("placement_id=" + placement_id); Properties pitch = SakaiBLTIUtil.getPropertiesFromPlacement(placement_id, ltiService); if (pitch == null) { M_log.debug("Error retrieving result_sourcedid information"); doError(request, response, theMap, "outcomes.sourcedid", "sourcedid", null); return; } String siteId = pitch.getProperty(LTIService.LTI_SITE_ID); Site site = null; try { site = SiteService.getSite(siteId); } catch (Exception e) { M_log.debug("Error retrieving result_sourcedid site: " + e.getLocalizedMessage(), e); } // Send a more generic message back to the caller if (site == null) { doError(request, response, theMap, "outcomes.sourcedid", "sourcedid", null); return; } // Check the message signature using OAuth String oauth_secret = pitch.getProperty(LTIService.LTI_SECRET); M_log.debug("oauth_secret: " + oauth_secret); oauth_secret = SakaiBLTIUtil.decryptSecret(oauth_secret); M_log.debug("oauth_secret (decrypted): " + oauth_secret); String URL = SakaiBLTIUtil.getOurServletPath(request); 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); String base_string = null; try { base_string = OAuthSignatureMethod.getBaseString(oam); } catch (Exception e) { M_log.error(e.getLocalizedMessage(), e); base_string = null; } try { oav.validateMessage(oam, acc); } catch (Exception e) { M_log.warn("Provider failed to validate message"); M_log.warn(e.getLocalizedMessage(), e); if (base_string != null) { M_log.warn(base_string); } doError(request, response, theMap, "outcome.no.validate", oauth_consumer_key, null); return; } // Check the signature of the sourcedid to make sure it was not altered String placement_secret = pitch.getProperty(LTIService.LTI_PLACEMENTSECRET); // Send a generic message back to the caller if (placement_secret == null) { doError(request, response, theMap, "outcomes.sourcedid", "sourcedid", null); return; } String pre_hash = placement_secret + ":::" + user_id + ":::" + placement_id; String received_signature = LegacyShaUtil.sha256Hash(pre_hash); M_log.debug("Received signature=" + signature + " received=" + received_signature); boolean matched = signature.equals(received_signature); String old_placement_secret = pitch.getProperty(LTIService.LTI_OLDPLACEMENTSECRET); if (old_placement_secret != null && !matched) { pre_hash = placement_secret + ":::" + user_id + ":::" + placement_id; received_signature = LegacyShaUtil.sha256Hash(pre_hash); M_log.debug("Received signature II=" + signature + " received=" + received_signature); matched = signature.equals(received_signature); } // Send a message back to the caller if (!matched) { doError(request, response, theMap, "outcomes.sourcedid", "sourcedid", null); return; } // Perform the message-specific handling if ("toolsetting".equals(message_type)) processSetting( request, response, lti_message_type, site, siteId, placement_id, pitch, user_id, theMap); if ("roster".equals(message_type)) processRoster( request, response, lti_message_type, site, siteId, placement_id, pitch, user_id, theMap); }
/** * Add a signature to the message. * * @throws URISyntaxException */ public void sign(OAuthAccessor accessor) throws IOException, OAuthException, URISyntaxException { OAuthSignatureMethod.newSigner(this, accessor).sign(this); }
protected void validateSignature(OAuthMessage message, OAuthAccessor accessor) throws OAuthException, IOException, URISyntaxException { message.requireParameters( OAuth.OAUTH_CONSUMER_KEY, OAuth.OAUTH_SIGNATURE_METHOD, OAuth.OAUTH_SIGNATURE); OAuthSignatureMethod.newSigner(message, accessor).validate(message); }
protected void validate(Map payload, boolean isTrustedConsumer) throws LTIException { // check parameters String lti_message_type = (String) payload.get(BasicLTIConstants.LTI_MESSAGE_TYPE); String lti_version = (String) payload.get(BasicLTIConstants.LTI_VERSION); String oauth_consumer_key = (String) payload.get("oauth_consumer_key"); String resource_link_id = (String) payload.get(BasicLTIConstants.RESOURCE_LINK_ID); String user_id = (String) payload.get(BasicLTIConstants.USER_ID); String context_id = (String) payload.get(BasicLTIConstants.CONTEXT_ID); boolean launch = true; if (BasicLTIUtil.equals(lti_message_type, "basic-lti-launch-request")) { launch = true; } else if (BasicLTIUtil.equals(lti_message_type, "ContentItemSelectionRequest")) { launch = false; } else { throw new LTIException("launch.invalid", "lti_message_type=" + lti_message_type, null); } if (!BasicLTIUtil.equals(lti_version, "LTI-1p0")) { throw new LTIException("launch.invalid", "lti_version=" + lti_version, null); } if (BasicLTIUtil.isBlank(oauth_consumer_key)) { throw new LTIException("launch.missing", "oauth_consumer_key", null); } if (launch && BasicLTIUtil.isBlank(resource_link_id)) { throw new LTIException("launch.missing", "resource_link_id", null); } if (BasicLTIUtil.isBlank(user_id)) { throw new LTIException("launch.missing", "user_id", null); } if (M_log.isDebugEnabled()) { M_log.debug("user_id=" + user_id); } // check tool_id String tool_id = (String) payload.get("tool_id"); if (tool_id == null) { throw new LTIException("launch.tool_id.required", null, null); } // Trim off the leading slash and any trailing space tool_id = tool_id.substring(1).trim(); if (M_log.isDebugEnabled()) { M_log.debug("tool_id=" + tool_id); } // store modified tool_id back in payload payload.put("tool_id", tool_id); final String allowedToolsConfig = ServerConfigurationService.getString("basiclti.provider.allowedtools", ""); final String[] allowedTools = allowedToolsConfig.split(":"); final List<String> allowedToolsList = Arrays.asList(allowedTools); if (launch && allowedTools != null && !allowedToolsList.contains(tool_id)) { throw new LTIException("launch.tool.notallowed", tool_id, null); } final Tool toolCheck = ToolManager.getTool(tool_id); if (launch && toolCheck == null) { throw new LTIException("launch.tool.notfound", tool_id, null); } // Check for the ext_sakai_provider_eid param. If set, this will contain the eid that we are to // use // in place of using the user_id parameter // WE still need that parameter though, so translate it from the given eid. boolean useProvidedEid = false; String ext_sakai_provider_eid = (String) payload.get(BasicLTIConstants.EXT_SAKAI_PROVIDER_EID); if (BasicLTIUtil.isNotBlank(ext_sakai_provider_eid)) { useProvidedEid = true; try { user_id = UserDirectoryService.getUserId(ext_sakai_provider_eid); } catch (Exception e) { M_log.error(e.getLocalizedMessage(), e); throw new LTIException( "launch.provided.eid.invalid", "ext_sakai_provider_eid=" + ext_sakai_provider_eid, e); } } if (M_log.isDebugEnabled()) { M_log.debug("ext_sakai_provider_eid=" + ext_sakai_provider_eid); } // Contextualize the context_id with the OAuth consumer key // Also use the resource_link_id for the context_id if we did not get a context_id // BLTI-31: if trusted, context_id is required and use the param without modification if (BasicLTIUtil.isBlank(context_id)) { if (isTrustedConsumer) { throw new LTIException("launch.missing", context_id, null); } else { context_id = "res:" + resource_link_id; payload.put(BasicLTIConstants.CONTEXT_ID, context_id); } } // Check if context_id is simply a ~. If so, get the id of that user's My Workspace site // and use that to construct the full context_id if (BasicLTIUtil.equals(context_id, "~")) { if (useProvidedEid) { String userSiteId = null; try { userSiteId = SiteService.getUserSiteId(user_id); } catch (Exception e) { M_log.warn("Failed to get My Workspace site for user_id:" + user_id); M_log.error(e.getLocalizedMessage(), e); throw new LTIException("launch.user.site.unknown", "user_id=" + user_id, e); } context_id = userSiteId; payload.put(BasicLTIConstants.CONTEXT_ID, context_id); } } if (M_log.isDebugEnabled()) { M_log.debug("context_id=" + context_id); } // Lookup the secret final String configPrefix = "basiclti.provider." + oauth_consumer_key + "."; final String oauth_secret = ServerConfigurationService.getString(configPrefix + "secret", null); if (oauth_secret == null) { throw new LTIException("launch.key.notfound", oauth_consumer_key, null); } final OAuthMessage oam = (OAuthMessage) payload.get("oauth_message"); final String forcedURIScheme = ServerConfigurationService.getString("basiclti.provider.forcedurischeme", null); if (forcedURIScheme != null) { try { URI testURI = new URI(oam.URL); URI newURI = new URI(forcedURIScheme, testURI.getSchemeSpecificPart(), null); oam.URL = newURI.toString(); } catch (URISyntaxException use) { } } final OAuthValidator oav = new SimpleOAuthValidator(); final OAuthConsumer cons = new OAuthConsumer( "about:blank#OAuth+CallBack+NotUsed", oauth_consumer_key, oauth_secret, null); final OAuthAccessor acc = new OAuthAccessor(cons); String base_string = null; try { base_string = OAuthSignatureMethod.getBaseString(oam); } catch (Exception e) { M_log.error(e.getLocalizedMessage(), e); base_string = null; } try { oav.validateMessage(oam, acc); } catch (Exception e) { M_log.warn("Provider failed to validate message"); M_log.warn(e.getLocalizedMessage(), e); if (base_string != null) { M_log.warn(base_string); } throw new LTIException("launch.no.validate", context_id, e); } final Session sess = SessionManager.getCurrentSession(); if (sess == null) { throw new LTIException("launch.no.session", context_id, null); } }