Java tutorial
/** * $URL$ * $Id$ * * Copyright (c) 2006-2009 The Sakai Foundation * * 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.opensource.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 org.sakaiproject.basiclti.util; import java.util.Properties; import java.util.Map; import java.util.TreeMap; import java.util.HashMap; import java.util.List; import java.util.ArrayList; import java.util.Iterator; import java.util.Enumeration; import java.net.URL; import java.net.URLEncoder; import javax.servlet.http.HttpServletRequest; import org.json.simple.JSONObject; import org.json.simple.JSONArray; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.imsglobal.basiclti.BasicLTIUtil; import org.imsglobal.basiclti.BasicLTIConstants; import org.imsglobal.lti2.LTI2Constants; import org.imsglobal.lti2.LTI2Vars; import org.imsglobal.lti2.LTI2Caps; import org.imsglobal.lti2.LTI2Util; import org.imsglobal.lti2.LTI2Messages; import org.imsglobal.lti2.ToolProxy; import org.imsglobal.lti2.ToolProxyBinding; import org.imsglobal.lti2.ContentItem; import org.imsglobal.lti2.objects.ToolConsumer; import org.imsglobal.lti2.LTI2Config; import org.sakaiproject.alias.api.AliasService; import org.sakaiproject.lti.api.LTIService; import org.sakaiproject.lti2.SakaiLTI2Config; import org.sakaiproject.tool.api.Session; import org.sakaiproject.tool.cover.SessionManager; import org.sakaiproject.tool.cover.ToolManager; import org.sakaiproject.event.cover.UsageSessionService; import org.sakaiproject.user.api.User; import org.sakaiproject.user.cover.UserDirectoryService; import org.sakaiproject.site.api.ToolConfiguration; import org.sakaiproject.tool.api.Placement; import org.sakaiproject.authz.cover.SecurityService; import org.sakaiproject.site.api.Site; import org.sakaiproject.site.cover.SiteService; import org.sakaiproject.api.privacy.PrivacyManager; import org.sakaiproject.authz.api.AuthzGroupService; import org.sakaiproject.authz.api.AuthzGroup; import org.sakaiproject.authz.api.GroupProvider; import org.sakaiproject.authz.api.Role; import org.sakaiproject.authz.api.Member; import org.sakaiproject.authz.api.GroupNotDefinedException; import org.sakaiproject.entity.api.ResourceProperties; import org.sakaiproject.component.cover.ServerConfigurationService; import org.sakaiproject.component.cover.ComponentManager; import org.sakaiproject.util.ResourceLoader; import org.sakaiproject.util.Web; import org.sakaiproject.portal.util.CSSUtils; import org.sakaiproject.portal.util.ToolUtils; import org.sakaiproject.linktool.LinkToolUtil; import org.sakaiproject.authz.api.SecurityAdvisor; import org.sakaiproject.authz.cover.SecurityService; import org.sakaiproject.service.gradebook.shared.AssignmentHasIllegalPointsException; import org.sakaiproject.service.gradebook.shared.CategoryDefinition; import org.sakaiproject.service.gradebook.shared.GradebookService; import org.sakaiproject.service.gradebook.shared.GradebookExternalAssessmentService; import org.sakaiproject.service.gradebook.shared.ConflictingAssignmentNameException; import org.sakaiproject.service.gradebook.shared.ConflictingExternalIdException; import org.sakaiproject.service.gradebook.shared.GradebookNotFoundException; import org.sakaiproject.service.gradebook.shared.Assignment; import org.sakaiproject.service.gradebook.shared.CommentDefinition; import net.oauth.OAuth; import net.oauth.OAuthAccessor; import net.oauth.OAuthConsumer; import net.oauth.OAuthMessage; import net.oauth.OAuthValidator; import net.oauth.SimpleOAuthValidator; import net.oauth.signature.OAuthSignatureMethod; /** * Some Sakai Utility code for IMS Basic LTI * This is mostly code to support the Sakai conventions for * making and launching BLTI resources within Sakai. */ @SuppressWarnings("deprecation") public class SakaiBLTIUtil { private static Log M_log = LogFactory.getLog(SakaiBLTIUtil.class); public static final boolean verbosePrint = false; // Turns off extensions public static final String LTI_STRICT = "basiclti.strict"; public static final String BASICLTI_OUTCOMES_ENABLED = "basiclti.outcomes.enabled"; public static final String BASICLTI_OUTCOMES_ENABLED_DEFAULT = "true"; public static final String BASICLTI_SETTINGS_ENABLED = "basiclti.settings.enabled"; public static final String BASICLTI_SETTINGS_ENABLED_DEFAULT = "true"; public static final String BASICLTI_ROSTER_ENABLED = "basiclti.roster.enabled"; public static final String BASICLTI_ROSTER_ENABLED_DEFAULT = "true"; public static final String BASICLTI_CONTENTLINK_ENABLED = "basiclti.contentlink.enabled"; public static final String BASICLTI_CONTENTLINK_ENABLED_DEFAULT = null; // i.e. false public static final String BASICLTI_CONSUMER_USERIMAGE_ENABLED = "basiclti.consumer.userimage.enabled"; public static final String INCOMING_ROSTER_ENABLED = "basiclti.incoming.roster.enabled"; public static final String BASICLTI_ENCRYPTION_KEY = "basiclti.encryption.key"; public static final String BASICLTI_LAUNCH_SESSION_TIMEOUT = "basiclti.launch.session.timeout"; public static final String SVC_tc_profile = "tc_profile"; public static final String SVC_tc_registration = "tc_registration"; public static final String SVC_Settings = "Settings"; public static final String SVC_Result = "Result"; public static final String LTI1_PATH = "/imsblis/service/"; public static final String LTI2_PATH = "/imsblis/lti2/"; public static final String SAKAI_CONTENTITEM_SELECTANY = "Sakai.contentitem.selectAny"; public static final String SAKAI_CONTENTITEM_SELECTFILE = "Sakai.contentitem.selectFile"; public static final String SAKAI_CONTENTITEM_SELECTIMPORT = "Sakai.contentitem.selectImport"; public static final String SAKAI_CONTENTITEM_SELECTLINK = "Sakai.contentitem.selectLink"; public static final String CANVAS_PLACEMENTS_COURSENAVIGATION = "Canvas.placements.courseNavigation"; public static final String CANVAS_PLACEMENTS_ACCOUNTNAVIGATION = "Canvas.placements.accountNavigation"; public static final String CANVAS_PLACEMENTS_ASSIGNMENTSELECTION = "Canvas.placements.assignmentSelection"; public static final String CANVAS_PLACEMENTS_LINKSELECTION = "Canvas.placements.linkSelection"; public static final String CANVAS_PLACEMENTS_CONTENTIMPORT = "Canvas.placements.contentImport"; public static void dPrint(String str) { if (verbosePrint) System.out.println(str); } // Retrieve the property from the configuration unless it // is overridden by the server configurtation (i.e. sakai.properties) public static String getCorrectProperty(Properties config, String propName, Placement placement) { // Check for global overrides in properties String allowSettings = ServerConfigurationService.getString(BASICLTI_SETTINGS_ENABLED, BASICLTI_SETTINGS_ENABLED_DEFAULT); if (LTIService.LTI_ALLOWSETTINGS.equals(propName) && !"true".equals(allowSettings)) return "false"; String allowRoster = ServerConfigurationService.getString(BASICLTI_ROSTER_ENABLED, BASICLTI_ROSTER_ENABLED_DEFAULT); if (LTIService.LTI_ALLOWROSTER.equals(propName) && !"true".equals(allowRoster)) return "false"; String allowContentLink = ServerConfigurationService.getString(BASICLTI_CONTENTLINK_ENABLED, BASICLTI_CONTENTLINK_ENABLED_DEFAULT); if ("contentlink".equals(propName) && !"true".equals(allowContentLink)) return null; // Check for explicit setting in properties String propertyName = placement.getToolId() + "." + propName; String propValue = ServerConfigurationService.getString(propertyName, null); if (propValue != null && propValue.trim().length() > 0) { // System.out.println("Sakai.home "+propName+"="+propValue); return propValue; } // Take it from the placement return config.getProperty("imsti." + propName, null); } // Look at a Placement and come up with the launch urls, and // other launch parameters to drive the launch. public static boolean loadFromPlacement(Properties info, Properties launch, Placement placement) { Properties config = placement.getConfig(); dPrint("Sakai properties=" + config); String launch_url = toNull(getCorrectProperty(config, LTIService.LTI_LAUNCH, placement)); setProperty(info, "launch_url", launch_url); if (launch_url == null) { String xml = toNull(getCorrectProperty(config, "xml", placement)); if (xml == null) return false; BasicLTIUtil.parseDescriptor(info, launch, xml); } String secret = getCorrectProperty(config, LTIService.LTI_SECRET, placement); // BLTI-195 - Compatibility mode for old-style encrypted secrets if (secret == null || secret.trim().length() < 1) { String eSecret = getCorrectProperty(config, "encryptedsecret", placement); if (eSecret != null && eSecret.trim().length() > 0) { secret = eSecret.trim() + ":" + SimpleEncryption.CIPHER; } } setProperty(info, LTIService.LTI_SECRET, secret); // This is not "consumerkey" on purpose - we are mimicking the old placement model setProperty(info, "key", getCorrectProperty(config, "key", placement)); setProperty(info, LTIService.LTI_DEBUG, getCorrectProperty(config, LTIService.LTI_DEBUG, placement)); setProperty(info, LTIService.LTI_FRAMEHEIGHT, getCorrectProperty(config, LTIService.LTI_FRAMEHEIGHT, placement)); setProperty(info, LTIService.LTI_NEWPAGE, getCorrectProperty(config, LTIService.LTI_NEWPAGE, placement)); setProperty(info, LTIService.LTI_TITLE, getCorrectProperty(config, "tooltitle", placement)); // Pull in and parse the custom parameters String customstr = toNull(getCorrectProperty(config, LTIService.LTI_CUSTOM, placement)); parseCustom(info, customstr); if (info.getProperty("launch_url", null) != null || info.getProperty("secure_launch_url", null) != null) { return true; } return false; } public static void parseCustom(Properties info, String customstr) { if (customstr != null) { String splitChar = "\n"; if (customstr.trim().indexOf("\n") == -1) splitChar = ";"; String[] params = customstr.split(splitChar); for (int i = 0; i < params.length; i++) { String param = params[i]; if (param == null) continue; if (param.length() < 1) continue; int pos = param.indexOf("="); if (pos < 1) continue; if (pos + 1 > param.length()) continue; String key = BasicLTIUtil.mapKeyName(param.substring(0, pos)); if (key == null) continue; String value = param.substring(pos + 1); if (value == null) continue; value = value.trim(); if (value.length() < 1) continue; setProperty(info, "custom_" + key, value); } } } public static String encryptSecret(String orig) { if (orig == null || orig.trim().length() < 1) return orig; String encryptionKey = ServerConfigurationService.getString(BASICLTI_ENCRYPTION_KEY, null); if (encryptionKey == null) return orig; // May throw runtime exception - just let it log as this is abnormal... String newsecret = SimpleEncryption.encrypt(encryptionKey, orig); return newsecret; } public static String decryptSecret(String orig) { if (orig == null || orig.trim().length() < 1) return orig; String encryptionKey = ServerConfigurationService.getString(BASICLTI_ENCRYPTION_KEY, null); if (encryptionKey == null) return orig; try { String newsecret = SimpleEncryption.decrypt(encryptionKey, orig); return newsecret; } catch (RuntimeException re) { dPrint("Exception when decrypting secret - this is normal if the secret is unencrypted"); return orig; } } public static boolean sakaiInfo(Properties props, Placement placement, ResourceLoader rb) { dPrint("placement=" + placement.getId()); dPrint("placement title=" + placement.getTitle()); String context = placement.getContext(); dPrint("ContextID=" + context); return sakaiInfo(props, context, placement.getId(), rb); } public static void addSiteInfo(Properties props, Properties lti2subst, Site site) { if (site != null) { String context_type = site.getType(); if (context_type != null && context_type.toLowerCase().contains("course")) { setProperty(props, BasicLTIConstants.CONTEXT_TYPE, BasicLTIConstants.CONTEXT_TYPE_COURSE_SECTION); setProperty(lti2subst, LTI2Vars.CONTEXT_TYPE, LTI2Vars.CONTEXT_TYPE_DEFAULT); } setProperty(props, BasicLTIConstants.CONTEXT_ID, site.getId()); setProperty(lti2subst, LTI2Vars.COURSESECTION_SOURCEDID, site.getId()); setProperty(lti2subst, LTI2Vars.CONTEXT_ID, site.getId()); setProperty(props, BasicLTIConstants.CONTEXT_LABEL, site.getTitle()); setProperty(lti2subst, LTI2Vars.COURSESECTION_LABEL, site.getTitle()); setProperty(lti2subst, LTI2Vars.CONTEXT_LABEL, site.getTitle()); setProperty(props, BasicLTIConstants.CONTEXT_TITLE, site.getTitle()); setProperty(lti2subst, LTI2Vars.COURSESECTION_LONGDESCRIPTION, site.getTitle()); setProperty(lti2subst, LTI2Vars.CONTEXT_TITLE, site.getTitle()); String courseRoster = getExternalRealmId(site.getId()); if (courseRoster != null) { setProperty(props, BasicLTIConstants.LIS_COURSE_OFFERING_SOURCEDID, courseRoster); setProperty(lti2subst, LTI2Vars.COURSESECTION_SOURCEDID, courseRoster); } } // Fix up the return Url String returnUrl = ServerConfigurationService.getString("basiclti.consumer_return_url", null); if (returnUrl == null) { returnUrl = getOurServerUrl() + LTI1_PATH + "return-url"; Session s = SessionManager.getCurrentSession(); if (s != null) { String controllingPortal = (String) s.getAttribute("sakai-controlling-portal"); if (controllingPortal == null) { returnUrl = returnUrl + "/site"; } else { returnUrl = returnUrl + "/" + controllingPortal; } } returnUrl = returnUrl + "/" + site.getId(); } setProperty(props, BasicLTIConstants.LAUNCH_PRESENTATION_RETURN_URL, returnUrl); } public static void addUserInfo(Properties ltiProps, Properties lti2subst, Map<String, Object> tool) { int releasename = getInt(tool.get(LTIService.LTI_SENDNAME)); int releaseemail = getInt(tool.get(LTIService.LTI_SENDEMAILADDR)); User user = UserDirectoryService.getCurrentUser(); if (user != null) { setProperty(ltiProps, BasicLTIConstants.USER_ID, user.getId()); setProperty(lti2subst, LTI2Vars.USER_ID, user.getId()); setProperty(ltiProps, BasicLTIConstants.LIS_PERSON_SOURCEDID, user.getEid()); setProperty(lti2subst, LTI2Vars.USER_USERNAME, user.getEid()); if (releasename == 1) { setProperty(ltiProps, BasicLTIConstants.LIS_PERSON_NAME_GIVEN, user.getFirstName()); setProperty(ltiProps, BasicLTIConstants.LIS_PERSON_NAME_FAMILY, user.getLastName()); setProperty(ltiProps, BasicLTIConstants.LIS_PERSON_NAME_FULL, user.getDisplayName()); setProperty(lti2subst, LTI2Vars.PERSON_NAME_GIVEN, user.getFirstName()); setProperty(lti2subst, LTI2Vars.PERSON_NAME_FAMILY, user.getLastName()); setProperty(lti2subst, LTI2Vars.PERSON_NAME_FULL, user.getDisplayName()); } if (releaseemail == 1) { setProperty(ltiProps, BasicLTIConstants.LIS_PERSON_CONTACT_EMAIL_PRIMARY, user.getEmail()); setProperty(lti2subst, LTI2Vars.PERSON_EMAIL_PRIMARY, user.getEmail()); // Only send the display ID if it's different to the EID. // the anonymous user has a null EID. if (user.getEid() != null && !user.getEid().equals(user.getDisplayId())) { setProperty(ltiProps, BasicLTIConstants.EXT_SAKAI_PROVIDER_DISPLAYID, user.getDisplayId()); } } } } public static String getRoleString(String context) { String theRole = LTI2Vars.MEMBERSHIP_ROLE_LEARNER; if (SecurityService.isSuperUser()) { theRole = LTI2Vars.MEMBERSHIP_ROLE_INSTRUCTOR + ",Administrator,urn:lti:instrole:ims/lis/Administrator,urn:lti:sysrole:ims/lis/Administrator"; } else if (SiteService.allowUpdateSite(context)) { theRole = LTI2Vars.MEMBERSHIP_ROLE_INSTRUCTOR; } return theRole; } public static void addRoleInfo(Properties props, Properties lti2subst, String context, String roleMapProp) { String theRole = getRoleString(context); setProperty(props, BasicLTIConstants.ROLES, theRole); setProperty(lti2subst, LTI2Vars.MEMBERSHIP_ROLE, theRole); String realmId = SiteService.siteReference(context); User user = null; Map<String, String> roleMap = convertRoleMapPropToMap(roleMapProp); try { user = UserDirectoryService.getCurrentUser(); if (user != null) { Role role = null; String roleId = null; AuthzGroup realm = ComponentManager.get(AuthzGroupService.class).getAuthzGroup(realmId); if (realm != null) role = realm.getUserRole(user.getId()); if (role != null) roleId = role.getId(); if (roleId != null && roleId.length() > 0) setProperty(props, "ext_sakai_role", roleId); if (roleMap.containsKey(roleId)) { setProperty(props, BasicLTIConstants.ROLES, roleMap.get(roleId)); setProperty(lti2subst, LTI2Vars.MEMBERSHIP_ROLE, roleMap.get(roleId)); } } } catch (GroupNotDefinedException e) { dPrint("SiteParticipantHelper.getExternalRealmId: site realm not found" + e.getMessage()); } // Check if there are sections the user is part of (may be more than one) String courseRoster = getExternalRealmId(context); if (user != null && courseRoster != null) { GroupProvider groupProvider = (GroupProvider) ComponentManager .get(org.sakaiproject.authz.api.GroupProvider.class); String[] courseRosters = groupProvider.unpackId(courseRoster); List<String> rosterList = new ArrayList<String>(); String userEid = user.getEid(); for (int i = 0; i < courseRosters.length; i++) { String providerId = courseRosters[i]; Map userRole = groupProvider.getUserRolesForGroup(providerId); if (userRole.containsKey(userEid)) { rosterList.add(providerId); } } if (rosterList.size() > 0) { String[] sArray = new String[rosterList.size()]; sArray = (String[]) rosterList.toArray(sArray); String providedGroups = groupProvider.packId(sArray); setProperty(props, "ext_sakai_section", providedGroups); } } } // Retrieve the Sakai information about users, etc. public static boolean sakaiInfo(Properties props, String context, String placementId, ResourceLoader rb) { Site site = null; try { site = SiteService.getSite(context); } catch (Exception e) { dPrint("No site/page associated with Launch context=" + context); return false; } // Add the generic information addGlobalData(site, props, null, rb); ToolConfiguration placement = SiteService.findTool(placementId); Properties config = placement.getConfig(); String roleMapProp = toNull(getCorrectProperty(config, "rolemap", placement)); addRoleInfo(props, null, context, roleMapProp); addSiteInfo(props, null, site); // Add Placement Information addPlacementInfo(props, placementId); return true; } public static void addPlacementInfo(Properties props, String placementId) { // Get the placement to see if we are to release information ToolConfiguration placement = SiteService.findTool(placementId); Properties config = placement.getConfig(); // Start setting the Basici LTI parameters setProperty(props, BasicLTIConstants.RESOURCE_LINK_ID, placementId); String pagetitle = toNull(getCorrectProperty(config, LTIService.LTI_PAGETITLE, placement)); if (pagetitle != null) setProperty(props, BasicLTIConstants.RESOURCE_LINK_TITLE, pagetitle); String tooltitle = toNull(getCorrectProperty(config, "tooltitle", placement)); if (tooltitle != null) setProperty(props, BasicLTIConstants.RESOURCE_LINK_DESCRIPTION, tooltitle); String releasename = toNull(getCorrectProperty(config, "releasename", placement)); String releaseemail = toNull(getCorrectProperty(config, "releaseemail", placement)); User user = UserDirectoryService.getCurrentUser(); PrivacyManager pm = (PrivacyManager) ComponentManager.get("org.sakaiproject.api.privacy.PrivacyManager"); // TODO: Think about anonymous if (user != null) { String context = placement.getContext(); boolean isViewable = pm.isViewable("/site/" + context, user.getId()); setProperty(props, "ext_sakai_privacy", isViewable ? "visible" : "hidden"); setProperty(props, BasicLTIConstants.USER_ID, user.getId()); if (ServerConfigurationService.getBoolean(BASICLTI_CONSUMER_USERIMAGE_ENABLED, true)) { String imageUrl = getOurServerUrl() + "/direct/profile/" + user.getId() + "/image"; setProperty(props, BasicLTIConstants.USER_IMAGE, imageUrl); } if ("on".equals(releasename)) { setProperty(props, BasicLTIConstants.LIS_PERSON_NAME_GIVEN, user.getFirstName()); setProperty(props, BasicLTIConstants.LIS_PERSON_NAME_FAMILY, user.getLastName()); setProperty(props, BasicLTIConstants.LIS_PERSON_NAME_FULL, user.getDisplayName()); } if ("on".equals(releaseemail)) { setProperty(props, BasicLTIConstants.LIS_PERSON_CONTACT_EMAIL_PRIMARY, user.getEmail()); setProperty(props, BasicLTIConstants.LIS_PERSON_SOURCEDID, user.getEid()); setProperty(props, "ext_sakai_eid", user.getEid()); } String assignment = null; // It is a little tricky - the tool configuration on/off decides whether // We check the serverCongigurationService true/false // We use the tool configuration to force outcomes off regardless of // server settings (i.e. an external tool never wants the outcomes // UI shown because it simply does not handle outcomes). String allowOutcomes = toNull(getCorrectProperty(config, LTIService.LTI_ALLOWOUTCOMES, placement)); if (!"off".equals(allowOutcomes)) { assignment = toNull(getCorrectProperty(config, "assignment", placement)); allowOutcomes = ServerConfigurationService.getString(BASICLTI_OUTCOMES_ENABLED, BASICLTI_OUTCOMES_ENABLED_DEFAULT); if (!"true".equals(allowOutcomes)) allowOutcomes = null; } String allowSettings = toNull(getCorrectProperty(config, LTIService.LTI_ALLOWSETTINGS, placement)); if (!"on".equals(allowSettings)) allowSettings = null; String allowRoster = toNull(getCorrectProperty(config, LTIService.LTI_ALLOWROSTER, placement)); if (!"on".equals(allowRoster)) allowRoster = null; String result_sourcedid = getSourceDID(user, placement, config); String theRole = getRoleString(context); // if ( result_sourcedid != null && theRole.indexOf(LTI2Vars.MEMBERSHIP_ROLE_LEARNER) >= 0 ) { if (result_sourcedid != null) { if ("true".equals(allowOutcomes) && assignment != null) { if (theRole.indexOf(LTI2Vars.MEMBERSHIP_ROLE_LEARNER) >= 0) { setProperty(props, BasicLTIConstants.LIS_RESULT_SOURCEDID, result_sourcedid); } setProperty(props, "ext_outcome_data_values_accepted", "text"); // SAK-25696 // New Basic Outcomes URL String outcome_url = ServerConfigurationService .getString("basiclti.consumer.ext_ims_lis_basic_outcome_url", null); if (outcome_url == null) outcome_url = getOurServerUrl() + LTI1_PATH; setProperty(props, "ext_ims_lis_basic_outcome_url", outcome_url); outcome_url = ServerConfigurationService .getString("basiclti.consumer." + BasicLTIConstants.LIS_OUTCOME_SERVICE_URL, null); if (outcome_url == null) outcome_url = getOurServerUrl() + LTI1_PATH; setProperty(props, BasicLTIConstants.LIS_OUTCOME_SERVICE_URL, outcome_url); } if ("on".equals(allowSettings)) { setProperty(props, "ext_ims_lti_tool_setting_id", result_sourcedid); String service_url = ServerConfigurationService .getString("basiclti.consumer.ext_ims_lti_tool_setting_url", null); if (service_url == null) service_url = getOurServerUrl() + LTI1_PATH; setProperty(props, "ext_ims_lti_tool_setting_url", service_url); } if ("on".equals(allowRoster)) { setProperty(props, "ext_ims_lis_memberships_id", result_sourcedid); String roster_url = ServerConfigurationService .getString("basiclti.consumer.ext_ims_lis_memberships_url", null); if (roster_url == null) roster_url = getOurServerUrl() + LTI1_PATH; setProperty(props, "ext_ims_lis_memberships_url", roster_url); } } // Send along the deprecated LinkTool encrypted session if requested String sendsession = toNull(getCorrectProperty(config, "ext_sakai_session", placement)); if ("true".equals(sendsession)) { Session s = SessionManager.getCurrentSession(); if (s != null) { String sessionid = s.getId(); if (sessionid != null) { sessionid = LinkToolUtil.encrypt(sessionid); setProperty(props, "ext_sakai_session", sessionid); } } } // Send along the SAK-28125 encrypted session if requested String encryptsession = toNull(getCorrectProperty(config, "ext_sakai_encrypted_session", placement)); String secret = toNull(getCorrectProperty(config, LTIService.LTI_SECRET, placement)); String key = toNull(getCorrectProperty(config, "key", placement)); if (secret != null && key != null && "true".equals(encryptsession) && !SecurityService.isSuperUser()) { secret = decryptSecret(secret); // sha1secret is 160-bits hex the sha1 for "secret" is // e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4 String sha1Secret = PortableShaUtil.sha1Hash(secret); Session s = SessionManager.getCurrentSession(); if (s != null) { String sessionid = s.getId(); if (sessionid != null) { sessionid = BlowFish.encrypt(sha1Secret, sessionid); setProperty(props, "ext_sakai_encrypted_session", sessionid); // Don't just change this as it will break existing connections // Especially to LTI tools written in Java with the default JCE setProperty(props, "ext_sakai_blowfish_length", "128"); } } } } // Send along the content link String contentlink = toNull(getCorrectProperty(config, "contentlink", placement)); if (contentlink != null) setProperty(props, "ext_resource_link_content", contentlink); } public static void addGlobalData(Site site, Properties props, Properties custom, ResourceLoader rb) { if (rb != null) setProperty(props, BasicLTIConstants.LAUNCH_PRESENTATION_LOCALE, rb.getLocale().toString()); // Add information about the Tool Consumer for LTI 1.x LTI2Config cnf = new SakaiLTI2Config(); setProperty(props, "tool_consumer_info_product_family_code", cnf.getProduct_family_product_code()); // Test 2.4 setProperty(props, "tool_consumer_info_version", cnf.getProduct_info_product_version()); // Test 2.5 // Get the organizational information setProperty(props, BasicLTIConstants.TOOL_CONSUMER_INSTANCE_GUID, ServerConfigurationService.getString("basiclti.consumer_instance_guid", null)); setProperty(props, BasicLTIConstants.TOOL_CONSUMER_INSTANCE_NAME, ServerConfigurationService.getString("basiclti.consumer_instance_name", null)); setProperty(props, BasicLTIConstants.TOOL_CONSUMER_INSTANCE_DESCRIPTION, ServerConfigurationService.getString("basiclti.consumer_instance_description", null)); setProperty(props, BasicLTIConstants.TOOL_CONSUMER_INSTANCE_CONTACT_EMAIL, ServerConfigurationService.getString("basiclti.consumer_instance_contact_email", null)); setProperty(props, BasicLTIConstants.TOOL_CONSUMER_INSTANCE_URL, ServerConfigurationService.getString("basiclti.consumer_instance_url", null)); // Send along the CSS URL String tool_css = ServerConfigurationService.getString("basiclti.consumer.launch_presentation_css_url", null); if (tool_css == null) tool_css = getOurServerUrl() + CSSUtils.getCssToolBase(); setProperty(props, BasicLTIConstants.LAUNCH_PRESENTATION_CSS_URL, tool_css); // Send along the CSS URL list String tool_css_all = ServerConfigurationService .getString("basiclti.consumer.ext_sakai_launch_presentation_css_url_all", null); if (site != null && tool_css_all == null) { tool_css_all = getOurServerUrl() + CSSUtils.getCssToolBase() + ',' + getOurServerUrl() + CSSUtils.getCssToolSkin(site); } setProperty(props, "ext_sakai_" + BasicLTIConstants.LAUNCH_PRESENTATION_CSS_URL + "_list", tool_css_all); // Let tools know we are coming from Sakai String sakaiVersion = ServerConfigurationService.getString("version.sakai", "2"); setProperty(props, "ext_lms", "sakai-" + sakaiVersion); setProperty(props, BasicLTIConstants.TOOL_CONSUMER_INFO_PRODUCT_FAMILY_CODE, "sakai"); setProperty(props, BasicLTIConstants.TOOL_CONSUMER_INFO_VERSION, sakaiVersion); setProperty(custom, LTI2Vars.TOOLCONSUMERINFO_PRODUCTFAMILYCODE, "sakai"); setProperty(custom, LTI2Vars.TOOLCONSUMERINFO_VERSION, sakaiVersion); // We pass this along in the Sakai world - it might // might be useful to the external tool String serverId = ServerConfigurationService.getServerId(); setProperty(props, "ext_sakai_serverid", serverId); setProperty(props, "ext_sakai_server", getOurServerUrl()); } // getProperty(String name); // Gnerate HTML from a descriptor and properties from public static String[] postLaunchHTML(String descriptor, String contextId, String resourceId, ResourceProperties props, ResourceLoader rb) { if (descriptor == null || contextId == null || resourceId == null) return postError("<p>" + getRB(rb, "error.descriptor", "Error, missing contextId, resourceid or descriptor") + "</p>"); // Add user, course, etc to the launch parameters Properties launch = new Properties(); if (!sakaiInfo(launch, contextId, resourceId, rb)) { return postError( "<p>" + getRB(rb, "error.info.resource", "Error, cannot load Sakai information for resource=") + resourceId + ".</p>"); } Properties info = new Properties(); if (!BasicLTIUtil.parseDescriptor(info, launch, descriptor)) { return postError( "<p>" + getRB(rb, "error.badxml.resource", "Error, cannot parse descriptor for resource=") + resourceId + ".</p>"); } return postLaunchHTML(info, launch, rb); } // This must return an HTML message as the [0] in the array // If things are successful - the launch URL is in [1] public static String[] postLaunchHTML(Map<String, Object> content, Map<String, Object> tool, LTIService ltiService, ResourceLoader rb) { if (content == null) { return postError( "<p>" + getRB(rb, "error.content.missing", "Content item is missing or improperly configured.") + "</p>"); } if (tool == null) { return postError("<p>" + getRB(rb, "error.tool.missing", "Tool item is missing or improperly configured.") + "</p>"); } int status = getInt(tool.get(LTIService.LTI_STATUS)); if (status == 1) return postError("<p>" + getRB(rb, "tool.disabled", "Tool is currently disabled") + "</p>"); // Go with the content url first String launch_url = (String) content.get(LTIService.LTI_LAUNCH); if (launch_url == null) launch_url = (String) tool.get(LTIService.LTI_LAUNCH); if (launch_url == null) return postError("<p>" + getRB(rb, "error.nolaunch", "This tool is not yet configured.") + "</p>"); String context = (String) content.get(LTIService.LTI_SITE_ID); Site site = null; try { site = SiteService.getSite(context); } catch (Exception e) { dPrint("No site/page associated with Launch context=" + context); return postError("<p>" + getRB(rb, "error.site.missing", "Cannot load site.") + context + "</p>"); } // Percolate up to get the other objects... Map<String, Object> proxyBinding = null; Map<String, Object> deploy = null; Long deployKey = getLongKey(tool.get(LTIService.LTI_DEPLOYMENT_ID)); if (deployKey >= 0) { deploy = ltiService.getDeployDao(deployKey); } Long toolKey = getLongKey(tool.get(LTIService.LTI_ID)); proxyBinding = ltiService.getProxyBindingDao(toolKey, context); Long toolVersion = getLongNull(tool.get(LTIService.LTI_VERSION)); boolean isLTI1 = toolVersion == null || (!toolVersion.equals(LTIService.LTI_VERSION_2)); M_log.debug("toolVersion=" + toolVersion + " isLTI1=" + isLTI1); // Start building up the properties Properties ltiProps = new Properties(); Properties toolProps = new Properties(); Properties lti2subst = new Properties(); if (isLTI1) { setProperty(ltiProps, BasicLTIConstants.LTI_VERSION, BasicLTIConstants.LTI_VERSION_1); } else { setProperty(ltiProps, BasicLTIConstants.LTI_VERSION, BasicLTIConstants.LTI_VERSION_2); } addGlobalData(site, ltiProps, lti2subst, rb); addSiteInfo(ltiProps, lti2subst, site); addRoleInfo(ltiProps, lti2subst, context, (String) tool.get("rolemap")); // This is for 1.2 - Not likely to be used // http://www.imsglobal.org/lti/ltiv1p2/ltiIMGv1p2.html if (deploy != null) { setProperty(lti2subst, LTI2Vars.TOOLCONSUMERPROFILE_URL, getOurServerUrl() + LTI2_PATH + SVC_tc_profile + "/" + (String) deploy.get(LTIService.LTI_CONSUMERKEY)); ; } String resource_link_id = "content:" + content.get(LTIService.LTI_ID); setProperty(ltiProps, BasicLTIConstants.RESOURCE_LINK_ID, resource_link_id); setProperty(lti2subst, "ResourceLink.id", resource_link_id); setProperty(toolProps, "launch_url", launch_url); String secret = (String) content.get(LTIService.LTI_SECRET); if (secret == null) secret = (String) tool.get(LTIService.LTI_SECRET); String key = (String) content.get(LTIService.LTI_CONSUMERKEY); if (key == null) key = (String) tool.get(LTIService.LTI_CONSUMERKEY); if (LTIService.LTI_SECRET_INCOMPLETE.equals(key) && LTIService.LTI_SECRET_INCOMPLETE.equals(secret)) { return postError( "<p>" + getRB(rb, "error.tool.partial", "Tool item is incomplete, missing a key and secret.") + "</p>"); } setProperty(toolProps, LTIService.LTI_SECRET, secret); setProperty(toolProps, "key", key); int debug = getInt(tool.get(LTIService.LTI_DEBUG)); if (debug == 2) debug = getInt(content.get(LTIService.LTI_DEBUG)); setProperty(toolProps, LTIService.LTI_DEBUG, debug + ""); int frameheight = getInt(tool.get(LTIService.LTI_FRAMEHEIGHT)); if (frameheight == 2) frameheight = getInt(content.get(LTIService.LTI_FRAMEHEIGHT)); setProperty(toolProps, LTIService.LTI_FRAMEHEIGHT, frameheight + ""); int newpage = getInt(tool.get(LTIService.LTI_NEWPAGE)); if (newpage == 2) newpage = getInt(content.get(LTIService.LTI_NEWPAGE)); setProperty(toolProps, LTIService.LTI_NEWPAGE, newpage + ""); String title = (String) content.get(LTIService.LTI_TITLE); if (title == null) title = (String) tool.get(LTIService.LTI_TITLE); if (title != null) { setProperty(ltiProps, BasicLTIConstants.RESOURCE_LINK_TITLE, title); setProperty(lti2subst, LTI2Vars.RESOURCELINK_TITLE, title); setProperty(lti2subst, LTI2Vars.RESOURCELINK_DESCRIPTION, title); } int releasename = getInt(tool.get(LTIService.LTI_SENDNAME)); int releaseemail = getInt(tool.get(LTIService.LTI_SENDEMAILADDR)); User user = UserDirectoryService.getCurrentUser(); if (user != null) { setProperty(ltiProps, BasicLTIConstants.USER_ID, user.getId()); setProperty(lti2subst, LTI2Vars.USER_ID, user.getId()); setProperty(ltiProps, BasicLTIConstants.LIS_PERSON_SOURCEDID, user.getEid()); setProperty(lti2subst, LTI2Vars.USER_USERNAME, user.getEid()); if (releasename == 1) { setProperty(ltiProps, BasicLTIConstants.LIS_PERSON_NAME_GIVEN, user.getFirstName()); setProperty(ltiProps, BasicLTIConstants.LIS_PERSON_NAME_FAMILY, user.getLastName()); setProperty(ltiProps, BasicLTIConstants.LIS_PERSON_NAME_FULL, user.getDisplayName()); setProperty(lti2subst, LTI2Vars.PERSON_NAME_GIVEN, user.getFirstName()); setProperty(lti2subst, LTI2Vars.PERSON_NAME_FAMILY, user.getLastName()); setProperty(lti2subst, LTI2Vars.PERSON_NAME_FULL, user.getDisplayName()); } if (releaseemail == 1) { setProperty(ltiProps, BasicLTIConstants.LIS_PERSON_CONTACT_EMAIL_PRIMARY, user.getEmail()); setProperty(lti2subst, LTI2Vars.PERSON_EMAIL_PRIMARY, user.getEmail()); // Only send the display ID if it's different to the EID. // the anonymous user has a null EID. if (user.getEid() != null && !user.getEid().equals(user.getDisplayId())) { setProperty(ltiProps, BasicLTIConstants.EXT_SAKAI_PROVIDER_DISPLAYID, user.getDisplayId()); } } } int allowoutcomes = getInt(tool.get(LTIService.LTI_ALLOWOUTCOMES)); int allowroster = getInt(tool.get(LTIService.LTI_ALLOWROSTER)); int allowsettings = getInt(tool.get(LTIService.LTI_ALLOWSETTINGS)); String placement_secret = (String) content.get(LTIService.LTI_PLACEMENTSECRET); // int tool_id = getInt(tool.get(LTIService.LTI_ID)); String result_sourcedid = getSourceDID(user, resource_link_id, placement_secret); if (result_sourcedid != null) { String theRole = getRoleString(context); if (allowoutcomes == 1) { // New Basic Outcomes URL String outcome_url = ServerConfigurationService .getString("basiclti.consumer.ext_ims_lis_basic_outcome_url", null); if (outcome_url == null) outcome_url = getOurServerUrl() + LTI1_PATH; setProperty(ltiProps, "ext_ims_lis_basic_outcome_url", outcome_url); outcome_url = ServerConfigurationService .getString("basiclti.consumer." + BasicLTIConstants.LIS_OUTCOME_SERVICE_URL, null); if (outcome_url == null) outcome_url = getOurServerUrl() + LTI1_PATH; setProperty(ltiProps, BasicLTIConstants.LIS_OUTCOME_SERVICE_URL, outcome_url); if (theRole.indexOf(LTI2Vars.MEMBERSHIP_ROLE_LEARNER) >= 0) { setProperty(ltiProps, BasicLTIConstants.LIS_RESULT_SOURCEDID, result_sourcedid); setProperty(lti2subst, LTI2Vars.RESULT_SOURCEDID, result_sourcedid); String result_url = getOurServerUrl() + LTI2_PATH + SVC_Result + "/" + result_sourcedid; setProperty(lti2subst, LTI2Vars.RESULT_URL, result_url); } } // We continue to support the old settings for LTI 2 see SAK-25621 if (allowsettings == 1) { setProperty(ltiProps, "ext_ims_lti_tool_setting_id", result_sourcedid); String service_url = ServerConfigurationService .getString("basiclti.consumer.ext_ims_lti_tool_setting_url", null); if (service_url == null) service_url = getOurServerUrl() + LTI1_PATH; setProperty(ltiProps, "ext_ims_lti_tool_setting_url", service_url); if (!isLTI1) { String settings_url = getOurServerUrl() + LTI2_PATH + SVC_Settings + "/"; setProperty(lti2subst, LTI2Vars.LTILINK_CUSTOM_URL, settings_url + LTI2Util.SCOPE_LtiLink + "/" + resource_link_id); setProperty(lti2subst, LTI2Vars.TOOLPROXYBINDING_CUSTOM_URL, settings_url + LTI2Util.SCOPE_ToolProxyBinding + "/" + resource_link_id); setProperty(lti2subst, LTI2Vars.TOOLPROXY_CUSTOM_URL, settings_url + LTI2Util.SCOPE_ToolProxy + "/" + key); } } if (allowroster == 1) { setProperty(ltiProps, "ext_ims_lis_memberships_id", result_sourcedid); String roster_url = ServerConfigurationService .getString("basiclti.consumer.ext_ims_lis_memberships_url", null); if (roster_url == null) roster_url = getOurServerUrl() + LTI1_PATH; setProperty(ltiProps, "ext_ims_lis_memberships_url", roster_url); } } // Merge all the sources of properties according to the arcane precedence for launch Properties custom = new Properties(); LTI2Util.mergeLTI2Custom(custom, (String) content.get(LTIService.LTI_SETTINGS)); LTI2Util.mergeLTI2Custom(custom, (String) tool.get(LTIService.LTI_SETTINGS)); LTI2Util.mergeLTI2Parameters(custom, (String) tool.get(LTIService.LTI_PARAMETER)); if (proxyBinding != null) { LTI2Util.mergeLTI2Custom(custom, (String) proxyBinding.get(LTIService.LTI_SETTINGS)); } if (deploy != null) { LTI2Util.mergeLTI2Custom(custom, (String) deploy.get(LTIService.LTI_SETTINGS)); } int allowCustom = getInt(tool.get(LTIService.LTI_ALLOWCUSTOM)); if (allowCustom == 1) LTI2Util.mergeLTI1Custom(custom, (String) content.get(LTIService.LTI_CUSTOM)); LTI2Util.mergeLTI1Custom(custom, (String) tool.get(LTIService.LTI_CUSTOM)); // System.out.println("ltiProps="+ltiProps); // System.out.println("toolProps="+toolProps); M_log.debug("lti2subst=" + lti2subst); M_log.debug("before custom=" + custom); LTI2Util.substituteCustom(custom, lti2subst); M_log.debug("after custom=" + custom); // Place the custom values into the launch LTI2Util.addCustomToLaunch(ltiProps, custom); // Check which kind of signing we are supposed to do String tool_proxy_binding = (String) tool.get("tool_proxy_binding"); if (tool_proxy_binding != null && tool_proxy_binding.trim().length() > 0) { ToolProxyBinding toolProxyBinding = new ToolProxyBinding(tool_proxy_binding); if (toolProxyBinding.enabledCapability(LTI2Messages.BASIC_LTI_LAUNCH_REQUEST, LTI2Caps.OAUTH_HMAC256)) { ltiProps.put(OAuth.OAUTH_SIGNATURE_METHOD, "HMAC-SHA256"); M_log.debug("Launching with SHA256 Signing"); } } return postLaunchHTML(toolProps, ltiProps, rb); } /** * An LTI 2.0 Registration launch * * This must return an HTML message as the [0] in the array * If things are successful - the launch URL is in [1] */ public static String[] postRegisterHTML(Long deployKey, Map<String, Object> tool, ResourceLoader rb, String placementId) { if (tool == null) { return postError("<p>" + getRB(rb, "error.tool.missing", "Tool item is missing or improperly configured.") + "</p>"); } int status = getInt(tool.get(LTIService.LTI_REG_STATE)); if (status != 0) return postError( "<p>" + getRB(rb, "error.lti2.badstate", "Tool is in the wrong state to register") + "</p>"); String launch_url = (String) tool.get(LTIService.LTI_REG_LAUNCH); if (launch_url == null) return postError( "<p>" + getRB(rb, "error.lti2.noreg", "This tool is has no registration url.") + "</p>"); String password = (String) tool.get(LTIService.LTI_REG_PASSWORD); String key = (String) tool.get(LTIService.LTI_REG_KEY); String consumerkey = (String) tool.get(LTIService.LTI_CONSUMERKEY); if (password == null || key == null || consumerkey == null) { return postError( "<p>" + getRB(rb, "error.lti2.partial", "Tool item is incomplete, missing a key and password.") + "</p>"); } // Start building up the properties Properties ltiProps = new Properties(); setProperty(ltiProps, BasicLTIConstants.LTI_VERSION, LTI2Constants.LTI2_VERSION_STRING); setProperty(ltiProps, LTI2Constants.REG_KEY, key); // Also duplicate reg_key as the proposed Tool Proxy GUID setProperty(ltiProps, LTI2Constants.TOOL_PROXY_GUID, key); // TODO: Lets show off and encrypt this secret too... setProperty(ltiProps, LTI2Constants.REG_PASSWORD, password); setProperty(ltiProps, BasicLTIUtil.BASICLTI_SUBMIT, getRB(rb, "launch.button", "Press to Launch External Tool")); setProperty(ltiProps, BasicLTIConstants.LTI_MESSAGE_TYPE, BasicLTIConstants.LTI_MESSAGE_TYPE_TOOLPROXYREGISTRATIONREQUEST); String serverUrl = getOurServerUrl(); setProperty(ltiProps, LTI2Constants.TC_PROFILE_URL, serverUrl + LTI2_PATH + SVC_tc_profile + "/" + consumerkey); setProperty(ltiProps, BasicLTIConstants.LAUNCH_PRESENTATION_RETURN_URL, serverUrl + "/portal/tool/" + placementId + "?panel=PostRegister&id=" + deployKey); int debug = getInt(tool.get(LTIService.LTI_DEBUG)); M_log.debug("ltiProps=" + ltiProps); boolean dodebug = debug == 1; String postData = BasicLTIUtil.postLaunchHTML(ltiProps, launch_url, dodebug, null); String[] retval = { postData, launch_url }; return retval; } /** * An LTI 2.0 Reregistration launch * * This must return an HTML message as the [0] in the array * If things are successful - the launch URL is in [1] */ public static String[] postReregisterHTML(Long deployKey, Map<String, Object> deploy, ResourceLoader rb, String placementId) { if (deploy == null) { return postError( "<p>" + getRB(rb, "error.deploy.missing", "Deployment is missing or improperly configured.") + "</p>"); } int status = getInt(deploy.get("reg_state")); if (status == 0) return postError("<p>" + getRB(rb, "error.deploy.badstate", "Deployment is in the wrong state to register") + "</p>"); // Figure out the launch URL to use unless we have been told otherwise String launch_url = (String) deploy.get("reg_launch"); // Find the global message for Reregistration String reg_profile = (String) deploy.get("reg_profile"); ToolProxy toolProxy = null; try { toolProxy = new ToolProxy(reg_profile); } catch (Throwable t) { return postError("<p>" + getRB(rb, "error.deploy.badproxy", "This deployment has a broken reg_profile.") + "</p>"); } JSONObject proxy_message = toolProxy.getMessageOfType("ToolProxyReregistrationRequest"); String re_path = toolProxy.getPathFromMessage(proxy_message); if (re_path != null) launch_url = re_path; if (launch_url == null) return postError("<p>" + getRB(rb, "error.deploy.noreg", "This deployment is has no registration url.") + "</p>"); String consumerkey = (String) deploy.get(LTIService.LTI_CONSUMERKEY); String secret = (String) deploy.get(LTIService.LTI_SECRET); // If secret is encrypted, decrypt it secret = decryptSecret(secret); if (secret == null || consumerkey == null) { return postError( "<p>" + getRB(rb, "error.deploy.partial", "Deployment is incomplete, missing a key and secret.") + "</p>"); } // Start building up the properties Properties ltiProps = new Properties(); setProperty(ltiProps, BasicLTIConstants.LTI_VERSION, LTI2Constants.LTI2_VERSION_STRING); setProperty(ltiProps, BasicLTIUtil.BASICLTI_SUBMIT, getRB(rb, "launch.button", "Press to Launch External Tool")); setProperty(ltiProps, BasicLTIConstants.LTI_MESSAGE_TYPE, BasicLTIConstants.LTI_MESSAGE_TYPE_TOOLPROXY_RE_REGISTRATIONREQUEST); String serverUrl = getOurServerUrl(); setProperty(ltiProps, LTI2Constants.TC_PROFILE_URL, serverUrl + LTI2_PATH + SVC_tc_profile + "/" + consumerkey); setProperty(ltiProps, BasicLTIConstants.LAUNCH_PRESENTATION_RETURN_URL, serverUrl + "/portal/tool/" + placementId + "?panel=PostRegister&id=" + deployKey); int debug = getInt(deploy.get(LTIService.LTI_DEBUG)); // Handle any subsisution variables from the message Properties lti2subst = new Properties(); addGlobalData(null, ltiProps, lti2subst, rb); if (deploy != null) { setProperty(lti2subst, LTI2Vars.TOOLCONSUMERPROFILE_URL, getOurServerUrl() + LTI2_PATH + SVC_tc_profile + "/" + (String) deploy.get(LTIService.LTI_CONSUMERKEY)); ; } Properties custom = new Properties(); JSONArray parameter = toolProxy.getParameterFromMessage(proxy_message); if (parameter != null) { LTI2Util.mergeLTI2Parameters(custom, parameter.toString()); M_log.debug("lti2subst=" + lti2subst); M_log.debug("before custom=" + custom); LTI2Util.substituteCustom(custom, lti2subst); M_log.debug("after custom=" + custom); // Merge the custom values into the launch LTI2Util.addCustomToLaunch(ltiProps, custom); } Map<String, String> extra = new HashMap<String, String>(); ltiProps = BasicLTIUtil.signProperties(ltiProps, launch_url, "POST", consumerkey, secret, null, null, null, extra); M_log.debug("signed ltiProps=" + ltiProps); boolean dodebug = debug == 1; String postData = BasicLTIUtil.postLaunchHTML(ltiProps, launch_url, dodebug, extra); String[] retval = { postData, launch_url }; return retval; } /** * Build a URL, Adding Sakai's CSRF token */ public static String addCSRFToken(String url) { Session session = SessionManager.getCurrentSession(); Object csrfToken = session.getAttribute(UsageSessionService.SAKAI_CSRF_SESSION_ATTRIBUTE); if (url.indexOf("?") < 0) { url = url + "?"; } else { url = url + "&"; } url = url + "sakai_csrf_token=" + URLEncoder.encode(csrfToken.toString()); return url; } /** * Create a ContentItem from the current request (may throw runtime) */ public static ContentItem getContentItemFromRequest(Map<String, Object> tool) { Placement placement = ToolManager.getCurrentPlacement(); String siteId = placement.getContext(); String toolSiteId = (String) tool.get(LTIService.LTI_SITE_ID); if (toolSiteId != null && !toolSiteId.equals(siteId)) { throw new RuntimeException("Incorrect site id"); } HttpServletRequest req = ToolUtils.getRequestFromThreadLocal(); String lti_log = req.getParameter("lti_log"); String lti_errorlog = req.getParameter("lti_errorlog"); if (lti_log != null) M_log.debug(lti_log); if (lti_errorlog != null) M_log.warn(lti_errorlog); ContentItem contentItem = new ContentItem(req); String oauth_consumer_key = req.getParameter("oauth_consumer_key"); String oauth_secret = (String) tool.get(LTIService.LTI_SECRET); oauth_secret = decryptSecret(oauth_secret); String URL = getOurServletPath(req); if (!contentItem.validate(oauth_consumer_key, oauth_secret, URL)) { M_log.warn("Provider failed to validate message: " + contentItem.getErrorMessage()); String base_string = contentItem.getBaseString(); if (base_string != null) M_log.warn("base_string=" + base_string); throw new RuntimeException("Failed OAuth validation"); } return contentItem; } /** * An LTI 2.0 ContentItemSelectionRequest launch * * This must return an HTML message as the [0] in the array * If things are successful - the launch URL is in [1] */ public static String[] postContentItemSelectionRequest(Long toolKey, Map<String, Object> tool, ResourceLoader rb, String contentReturn, Properties dataProps) { if (tool == null) { return postError( "<p>" + getRB(rb, "error.tool.missing", "Tool is missing or improperly configured.") + "</p>"); } String launch_url = (String) tool.get("launch"); if (launch_url == null) return postError("<p>" + getRB(rb, "error.tool.noreg", "This tool is has no launch url.") + "</p>"); String consumerkey = (String) tool.get(LTIService.LTI_CONSUMERKEY); String secret = (String) tool.get(LTIService.LTI_SECRET); // If secret is encrypted, decrypt it secret = decryptSecret(secret); if (secret == null || consumerkey == null) { return postError("<p>" + getRB(rb, "error.tool.partial", "Tool is incomplete, missing a key and secret.") + "</p>"); } // Start building up the properties Properties ltiProps = new Properties(); setProperty(ltiProps, BasicLTIConstants.LTI_VERSION, BasicLTIConstants.LTI_VERSION_1); setProperty(ltiProps, BasicLTIUtil.BASICLTI_SUBMIT, getRB(rb, "launch.button", "Press to Launch External Tool")); setProperty(ltiProps, BasicLTIConstants.LTI_MESSAGE_TYPE, LTI2Messages.CONTENT_ITEM_SELECTION_REQUEST); setProperty(ltiProps, ContentItem.ACCEPT_MEDIA_TYPES, ContentItem.MEDIA_LTILINKITEM); setProperty(ltiProps, BasicLTIConstants.ACCEPT_PRESENTATION_DOCUMENT_TARGETS, "iframe,window"); // Nice to add overlay setProperty(ltiProps, BasicLTIConstants.ACCEPT_UNSIGNED, "true"); setProperty(ltiProps, BasicLTIConstants.ACCEPT_MULTIPLE, "false"); setProperty(ltiProps, BasicLTIConstants.ACCEPT_COPY_ADVICE, "false"); // ??? setProperty(ltiProps, BasicLTIConstants.AUTO_CREATE, "true"); setProperty(ltiProps, BasicLTIConstants.CAN_CONFIRM, "false"); // setProperty(ltiProps, BasicLTIConstants.TITLE, ""); // setProperty(ltiProps, BasicLTIConstants.TEXT, ""); // Pull in additonal data JSONObject dataJSON = new JSONObject(); Enumeration en = dataProps.keys(); while (en.hasMoreElements()) { String key = (String) en.nextElement(); String value = dataProps.getProperty(key); if (value == null) continue; // Allow overrides if (BasicLTIConstants.ACCEPT_MEDIA_TYPES.equals(key)) { setProperty(ltiProps, BasicLTIConstants.ACCEPT_MEDIA_TYPES, value); continue; } else if (BasicLTIConstants.ACCEPT_PRESENTATION_DOCUMENT_TARGETS.equals(key)) { setProperty(ltiProps, BasicLTIConstants.ACCEPT_PRESENTATION_DOCUMENT_TARGETS, value); continue; } else if (BasicLTIConstants.ACCEPT_UNSIGNED.equals(key)) { setProperty(ltiProps, BasicLTIConstants.ACCEPT_UNSIGNED, value); continue; } else if (BasicLTIConstants.AUTO_CREATE.equals(key)) { setProperty(ltiProps, BasicLTIConstants.AUTO_CREATE, value); continue; } else if (BasicLTIConstants.CAN_CONFIRM.equals(key)) { setProperty(ltiProps, BasicLTIConstants.CAN_CONFIRM, value); continue; } else if (BasicLTIConstants.TITLE.equals(key)) { setProperty(ltiProps, BasicLTIConstants.TITLE, value); continue; } else if (BasicLTIConstants.TEXT.equals(key)) { setProperty(ltiProps, BasicLTIConstants.TEXT, value); continue; } // Pass in data for use to get back. dataJSON.put(key, value); } setProperty(ltiProps, BasicLTIConstants.DATA, dataJSON.toString()); setProperty(ltiProps, BasicLTIConstants.CONTENT_ITEM_RETURN_URL, contentReturn); // This must always be there String context = (String) tool.get(LTIService.LTI_SITE_ID); Site site = null; try { site = SiteService.getSite(context); } catch (Exception e) { dPrint("No site/page associated with Launch context=" + context); return postError("<p>" + getRB(rb, "error.site.missing", "Cannot load site.") + context + "</p>"); } Properties lti2subst = new Properties(); addGlobalData(site, ltiProps, lti2subst, rb); addSiteInfo(ltiProps, lti2subst, site); addRoleInfo(ltiProps, lti2subst, context, (String) tool.get("rolemap")); addUserInfo(ltiProps, lti2subst, tool); int debug = getInt(tool.get(LTIService.LTI_DEBUG)); debug = 1; String customstr = toNull((String) tool.get(LTIService.LTI_CUSTOM)); parseCustom(ltiProps, customstr); Map<String, String> extra = new HashMap<String, String>(); ltiProps = BasicLTIUtil.signProperties(ltiProps, launch_url, "POST", consumerkey, secret, null, null, null, extra); M_log.debug("signed ltiProps=" + ltiProps); boolean dodebug = debug == 1; String postData = BasicLTIUtil.postLaunchHTML(ltiProps, launch_url, dodebug, extra); String[] retval = { postData, launch_url }; return retval; } // This must return an HTML message as the [0] in the array // If things are successful - the launch URL is in [1] public static String[] postLaunchHTML(String placementId, ResourceLoader rb) { if (placementId == null) return postError("<p>" + getRB(rb, "error.missing", "Error, missing placementId") + "</p>"); ToolConfiguration placement = SiteService.findTool(placementId); if (placement == null) return postError( "<p>" + getRB(rb, "error.load", "Error, cannot load placement=") + placementId + ".</p>"); // Add user, course, etc to the launch parameters Properties ltiProps = new Properties(); if (!sakaiInfo(ltiProps, placement, rb)) { return postError( "<p>" + getRB(rb, "error.missing", "Error, cannot load Sakai information for placement=") + placementId + ".</p>"); } // Retrieve the launch detail Properties toolProps = new Properties(); if (!loadFromPlacement(toolProps, ltiProps, placement)) { return postError("<p>" + getRB(rb, "error.nolaunch", "Not Configured.") + "</p>"); } return postLaunchHTML(toolProps, ltiProps, rb); } public static String[] postLaunchHTML(Properties toolProps, Properties ltiProps, ResourceLoader rb) { String launch_url = toolProps.getProperty("secure_launch_url"); if (launch_url == null) launch_url = toolProps.getProperty("launch_url"); if (launch_url == null) return postError("<p>" + getRB(rb, "error.missing", "Not configured") + "</p>"); String org_guid = ServerConfigurationService.getString("basiclti.consumer_instance_guid", null); String org_desc = ServerConfigurationService.getString("basiclti.consumer_instance_description", null); String org_url = ServerConfigurationService.getString("basiclti.consumer_instance_url", null); // Look up the LMS-wide secret and key - default key is guid String key = getToolConsumerInfo(launch_url, "key"); if (key == null) key = org_guid; String secret = getToolConsumerInfo(launch_url, LTIService.LTI_SECRET); // Demand key/secret in a pair if (key == null || secret == null) { key = null; secret = null; } // If we do not have LMS-wide info, use the local key/secret if (secret == null) { secret = toNull(toolProps.getProperty(LTIService.LTI_SECRET)); key = toNull(toolProps.getProperty("key")); } // If secret is encrypted, decrypt it secret = decryptSecret(secret); // Pull in all of the custom parameters for (Object okey : toolProps.keySet()) { String skey = (String) okey; if (!skey.startsWith(BasicLTIConstants.CUSTOM_PREFIX)) continue; String value = toolProps.getProperty(skey); if (value == null) continue; setProperty(ltiProps, skey, value); } String oauth_callback = ServerConfigurationService.getString("basiclti.oauth_callback", null); // Too bad there is not a better default callback url for OAuth // Actually since we are using signing-only, there is really not much point // In OAuth 6.2.3, this is after the user is authorized if (oauth_callback == null) oauth_callback = "about:blank"; setProperty(ltiProps, "oauth_callback", oauth_callback); setProperty(ltiProps, BasicLTIUtil.BASICLTI_SUBMIT, getRB(rb, "launch.button", "Press to Launch External Tool")); // Sanity checks if (secret == null) { return postError("<p>" + getRB(rb, "error.nosecret", "Error - must have a secret.") + "</p>"); } if (secret != null && key == null) { return postError("<p>" + getRB(rb, "error.nokey", "Error - must have a secret and a key.") + "</p>"); } Map<String, String> extra = new HashMap<String, String>(); ltiProps = BasicLTIUtil.signProperties(ltiProps, launch_url, "POST", key, secret, org_guid, org_desc, org_url, extra); if (ltiProps == null) return postError("<p>" + getRB(rb, "error.sign", "Error signing message.") + "</p>"); dPrint("LAUNCH III=" + ltiProps); String debugProperty = toolProps.getProperty(LTIService.LTI_DEBUG); boolean dodebug = "on".equals(debugProperty) || "1".equals(debugProperty); String postData = BasicLTIUtil.postLaunchHTML(ltiProps, launch_url, dodebug, extra); String[] retval = { postData, launch_url }; return retval; } public static String getSourceDID(User user, Placement placement, Properties config) { String placementSecret = toNull(getCorrectProperty(config, "placementsecret", placement)); if (placementSecret == null) return null; return getSourceDID(user, placement.getId(), placementSecret); } public static String getSourceDID(User user, String placeStr, String placementSecret) { if (placementSecret == null) return null; String suffix = ":::" + user.getId() + ":::" + placeStr; String base_string = placementSecret + suffix; String signature = LegacyShaUtil.sha256Hash(base_string); return signature + suffix; } public static String[] postError(String str) { String[] retval = { str }; return retval; } public static String getRB(ResourceLoader rb, String key, String def) { if (rb == null) return def; return rb.getString(key, def); } // To make absolutely sure we never send an XSS, we clean these values public static void setProperty(Properties props, String key, String value) { if (value == null) return; if (props == null) return; value = Web.cleanHtml(value); if (value.trim().length() < 1) return; props.setProperty(key, value); } private static String getExternalRealmId(String siteId) { String realmId = SiteService.siteReference(siteId); String rv = null; try { AuthzGroup realm = ComponentManager.get(AuthzGroupService.class).getAuthzGroup(realmId); rv = realm.getProviderGroupId(); } catch (GroupNotDefinedException e) { dPrint("SiteParticipantHelper.getExternalRealmId: site realm not found" + e.getMessage()); } return rv; } // getExternalRealmId // Look through a series of secrets from the properties based on the launchUrl private static String getToolConsumerInfo(String launchUrl, String data) { String default_secret = ServerConfigurationService.getString("basiclti.consumer_instance_" + data, null); dPrint("launchUrl = " + launchUrl); URL url = null; try { url = new URL(launchUrl); } catch (Exception e) { url = null; } if (url == null) return default_secret; String hostName = url.getHost(); dPrint("host = " + hostName); if (hostName == null || hostName.length() < 1) return default_secret; // Look for the property starting with the full name String org_info = ServerConfigurationService .getString("basiclti.consumer_instance_" + data + "." + hostName, null); if (org_info != null) return org_info; for (int i = 0; i < hostName.length(); i++) { if (hostName.charAt(i) != '.') continue; if (i > hostName.length() - 2) continue; String hostPart = hostName.substring(i + 1); String propName = "basiclti.consumer_instance_" + data + "." + hostPart; org_info = ServerConfigurationService.getString(propName, null); if (org_info != null) return org_info; } return default_secret; } // expected_oauth_key can be null - if it is non-null it must match the key in the request public static Object validateMessage(HttpServletRequest request, String URL, String oauth_secret, String expected_oauth_key) { oauth_secret = decryptSecret(oauth_secret); return BasicLTIUtil.validateMessage(request, URL, oauth_secret, expected_oauth_key); } // Returns: // String implies error // Boolean.TRUE - Sourcedid checks out // Boolean.FALSE - Sourcedid or secret fail public static Object checkSourceDid(String sourcedid, HttpServletRequest request, LTIService ltiService) { return handleGradebook(sourcedid, request, ltiService, false, false, null, null); } // Grade retrieval Map<String, Object> with "grade" => Double and "comment" => String public static Object getGrade(String sourcedid, HttpServletRequest request, LTIService ltiService) { return handleGradebook(sourcedid, request, ltiService, true, false, null, null); } // Boolean.TRUE - Grade updated public static Object setGrade(String sourcedid, HttpServletRequest request, LTIService ltiService, Double grade, String comment) { return handleGradebook(sourcedid, request, ltiService, false, false, grade, comment); } // Boolean.TRUE - Grade deleted public static Object deleteGrade(String sourcedid, HttpServletRequest request, LTIService ltiService) { return handleGradebook(sourcedid, request, ltiService, false, true, null, null); } // Quite a long bit of code private static Object handleGradebook(String sourcedid, HttpServletRequest request, LTIService ltiService, boolean isRead, boolean isDelete, Double theGrade, String comment) { // 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) { return "Unable to decrypt result_sourcedid=" + sourcedid; } M_log.debug("signature=" + signature); M_log.debug("user_id=" + user_id); M_log.debug("placement_id=" + placement_id); Properties pitch = getPropertiesFromPlacement(placement_id, ltiService); if (pitch == null) { return "Error retrieving result_sourcedid information"; } String siteId = pitch.getProperty(LTIService.LTI_SITE_ID); Site site = null; try { site = SiteService.getSite(siteId); } catch (Exception e) { return "Error retrieving result_sourcedid site: " + e.getLocalizedMessage(); } // Check the message signature using OAuth String oauth_secret = pitch.getProperty(LTIService.LTI_SECRET); M_log.debug("oauth_secret: " + oauth_secret); oauth_secret = decryptSecret(oauth_secret); M_log.debug("oauth_secret (decrypted): " + oauth_secret); String oauth_consumer_key = pitch.getProperty(LTIService.LTI_CONSUMERKEY); M_log.debug("oauth_consumer_key: " + oauth_consumer_key); String URL = getOurServletPath(request); // Validate the incoming message Object retval = validateMessage(request, URL, oauth_secret, oauth_consumer_key); if (retval instanceof String) return retval; // Check the signature of the sourcedid to make sure it was not altered String placement_secret = pitch.getProperty(LTIService.LTI_PLACEMENTSECRET); if (placement_secret == null) { return "Could not find placement secret"; } 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); } if (!matched) return "Sourcedid signature did not match"; // If we are not supposed to lookup or set the grade, we are done if (isRead == false && isDelete == false && theGrade == null) return new Boolean(matched); // Look up the assignment so we can find the max points GradebookService g = (GradebookService) ComponentManager .get("org.sakaiproject.service.gradebook.GradebookService"); // Make sure the user exists in the site boolean userExistsInSite = false; try { Member member = site.getMember(user_id); if (member != null) userExistsInSite = true; } catch (Exception e) { M_log.warn(e.getLocalizedMessage() + " siteId=" + siteId, e); return "User not found in site"; } // Make sure the placement is configured to receive grades String assignment = pitch.getProperty("assignment"); M_log.debug("ASSN=" + assignment); if (assignment == null) { return "Assignment not set in placement"; } Assignment assignmentObject = null; pushAdvisor(); try { List gradebookAssignments = g.getAssignments(siteId); for (Iterator i = gradebookAssignments.iterator(); i.hasNext();) { Assignment gAssignment = (Assignment) i.next(); if (gAssignment.isExternallyMaintained()) continue; if (assignment.equals(gAssignment.getName())) { assignmentObject = gAssignment; break; } } } catch (Exception e) { assignmentObject = null; // Just to make double sure } // Attempt to add assignment to grade book if (assignmentObject == null && g.isGradebookDefined(siteId)) { try { assignmentObject = new Assignment(); assignmentObject.setPoints(Double.valueOf(100)); assignmentObject.setExternallyMaintained(false); assignmentObject.setName(assignment); assignmentObject.setReleased(true); assignmentObject.setUngraded(false); g.addAssignment(siteId, assignmentObject); M_log.info("Added assignment: " + assignment); } catch (ConflictingAssignmentNameException e) { M_log.warn("ConflictingAssignmentNameException while adding assignment" + e.getMessage()); assignmentObject = null; // Just to make sure } catch (Exception e) { M_log.warn( "GradebookNotFoundException (may be because GradeBook has not yet been added to the Site) " + e.getMessage()); assignmentObject = null; // Just to make double sure } } // Now read, set, or delete the grade... Session sess = SessionManager.getCurrentSession(); String message = null; try { // Indicate "who" is setting this grade - needs to be a real user account String gb_user_id = ServerConfigurationService.getString("basiclti.outcomes.userid", "admin"); String gb_user_eid = ServerConfigurationService.getString("basiclti.outcomes.usereid", gb_user_id); sess.setUserId(gb_user_id); sess.setUserEid(gb_user_eid); if (isRead) { String actualGrade = g.getAssignmentScoreString(siteId, assignmentObject.getId(), user_id); Double dGrade = null; if (actualGrade != null && actualGrade.length() > 0) { dGrade = new Double(actualGrade); dGrade = dGrade / assignmentObject.getPoints(); } CommentDefinition commentDef = g.getAssignmentScoreComment(siteId, assignmentObject.getId(), user_id); message = "Result read"; Map<String, Object> retMap = new TreeMap<String, Object>(); retMap.put("grade", dGrade); retMap.put("comment", commentDef.getCommentText()); retval = retMap; } else if (isDelete) { g.setAssignmentScoreString(siteId, assignmentObject.getId(), user_id, null, "External Outcome"); M_log.info("Delete Score site=" + siteId + " assignment=" + assignment + " user_id=" + user_id); message = "Result deleted"; retval = Boolean.TRUE; } else { if (theGrade < 0.0 || theGrade > 1.0) { throw new Exception("Grade out of range"); } theGrade = theGrade * assignmentObject.getPoints(); g.setAssignmentScoreString(siteId, assignmentObject.getId(), user_id, String.valueOf(theGrade), "External Outcome"); g.setAssignmentScoreComment(siteId, assignmentObject.getId(), user_id, comment); M_log.info("Stored Score=" + siteId + " assignment=" + assignment + " user_id=" + user_id + " score=" + theGrade); message = "Result replaced"; retval = Boolean.TRUE; } } catch (Exception e) { retval = "Grade failure " + e.getMessage() + " siteId=" + siteId; } finally { sess.invalidate(); // Make sure to leave no traces popAdvisor(); } return retval; } // Extract the necessary properties from a placement public static Properties getPropertiesFromPlacement(String placement_id, LTIService ltiService) { // These are the fields from a placement - they are not an exact match // for the fields in tool/content String[] fieldList = { "key", LTIService.LTI_SECRET, LTIService.LTI_PLACEMENTSECRET, LTIService.LTI_OLDPLACEMENTSECRET, LTIService.LTI_ALLOWSETTINGS, "assignment", LTIService.LTI_ALLOWROSTER, "releasename", "releaseemail", "toolsetting" }; Properties retval = new Properties(); String siteId = null; if (isPlacement(placement_id)) { ToolConfiguration placement = null; Properties config = null; try { placement = SiteService.findTool(placement_id); config = placement.getConfig(); siteId = placement.getSiteId(); } catch (Exception e) { M_log.debug("Error getPropertiesFromPlacement: " + e.getLocalizedMessage(), e); return null; } retval.setProperty("placementId", placement_id); retval.setProperty(LTIService.LTI_SITE_ID, siteId); for (String field : fieldList) { String value = toNull(getCorrectProperty(config, field, placement)); if (field.equals("toolsetting")) { value = config.getProperty("toolsetting", null); field = LTIService.LTI_SETTINGS; } if (value == null) continue; if (field.equals("releasename")) field = LTIService.LTI_SENDNAME; if (field.equals("releaseemail")) field = LTIService.LTI_SENDEMAILADDR; if (field.equals("key")) field = LTIService.LTI_CONSUMERKEY; retval.setProperty(field, value); } } else { // Get information from content item Map<String, Object> content = null; Map<String, Object> tool = null; String contentStr = placement_id.substring(8); Long contentKey = getLongKey(contentStr); if (contentKey < 0) return null; // Leave off the siteId - bypass all checking - because we need to // finde the siteId from the content item content = ltiService.getContentDao(contentKey); if (content == null) return null; siteId = (String) content.get(LTIService.LTI_SITE_ID); if (siteId == null) return null; retval.setProperty("contentKey", contentStr); retval.setProperty(LTIService.LTI_SITE_ID, siteId); Long toolKey = getLongKey(content.get(LTIService.LTI_TOOL_ID)); if (toolKey < 0) return null; tool = ltiService.getToolDao(toolKey, siteId); if (tool == null) return null; // Adjust the content items based on the tool items ltiService.filterContent(content, tool); for (String formInput : LTIService.TOOL_MODEL) { Properties info = parseFormString(formInput); String field = info.getProperty("field", null); String type = info.getProperty("type", null); Object o = tool.get(field); if (o instanceof String) { retval.setProperty(field, (String) o); continue; } if ("checkbox".equals(type)) { int check = getInt(o); if (check == 1) { retval.setProperty(field, "on"); } else { retval.setProperty(field, "off"); } } } for (String formInput : LTIService.CONTENT_MODEL) { Properties info = parseFormString(formInput); String field = info.getProperty("field", null); String type = info.getProperty("type", null); Object o = content.get(field); if (o instanceof String) { retval.setProperty(field, (String) o); continue; } if ("checkbox".equals(type)) { int check = getInt(o); if (check == 1) { retval.setProperty(field, "on"); } else { retval.setProperty(field, "off"); } } } retval.setProperty("assignment", (String) content.get("title")); } return retval; } public static boolean isPlacement(String placement_id) { if (placement_id == null) return false; return !(placement_id.startsWith("content:") && placement_id.length() > 8); } // Since ServerConfigurationService.getServerUrl() is wonky because it sometimes looks // at request.getServerName() instead of the serverUrl property we have our own // priority to determine our current url. // BLTI-273 public static String getOurServerUrl() { String ourUrl = ServerConfigurationService.getString("sakai.lti.serverUrl"); if (ourUrl == null || ourUrl.equals("")) ourUrl = ServerConfigurationService.getString("serverUrl"); if (ourUrl == null || ourUrl.equals("")) ourUrl = ServerConfigurationService.getServerUrl(); if (ourUrl == null || ourUrl.equals("")) ourUrl = "http://127.0.0.1:8080"; if (ourUrl.endsWith("/") && ourUrl.length() > 2) ourUrl = ourUrl.substring(0, ourUrl.length() - 1); return ourUrl; } public static String getOurServletPath(HttpServletRequest request) { String URLstr = request.getRequestURL().toString(); String retval = URLstr.replaceFirst("^https??://[^/]*", getOurServerUrl()); return retval; } public static String toNull(String str) { if (str == null) return null; if (str.trim().length() < 1) return null; return str; } // Pull in a few things to avoid circular dependency public static int getInt(Object o) { if (o instanceof String) { try { return (new Integer((String) o)).intValue(); } catch (Exception e) { return -1; } } if (o instanceof Number) return ((Number) o).intValue(); return -1; } public static String[] positional = { "field", "type" }; public static Properties parseFormString(String str) { Properties op = new Properties(); String[] pairs = str.split(":"); int i = 0; for (String s : pairs) { String[] kv = s.split("="); if (kv.length == 2) { op.setProperty(kv[0], kv[1]); } else if (kv.length == 1 && i < positional.length) { op.setProperty(positional[i++], kv[0]); } else { // TODO : Log something here } } return op; } public static Long getLongKey(Object key) { return getLong(key); } public static Long getLong(Object key) { Long retval = getLongNull(key); if (retval != null) return retval; return new Long(-1); } public static Long getLongNull(Object key) { if (key == null) return null; if (key instanceof Number) return new Long(((Number) key).longValue()); if (key instanceof String) { if (((String) key).length() < 1) return new Long(-1); try { return new Long((String) key); } catch (Exception e) { return null; } } return null; } /** * Setup a security advisor. */ public static void pushAdvisor() { // setup a security advisor SecurityService.pushAdvisor(new SecurityAdvisor() { public SecurityAdvice isAllowed(String userId, String function, String reference) { return SecurityAdvice.ALLOWED; } }); } /** * Remove our security advisor. */ public static void popAdvisor() { SecurityService.popAdvisor(); } /** * Converts a string from a comma-separated list of role maps to a Map<String, String>. * Each role mapping in the string should be of the form <sakairole>:<ltirole>. */ public static Map<String, String> convertRoleMapPropToMap(String roleMapProp) { Map<String, String> roleMap = new HashMap<String, String>(); if (roleMapProp == null) return roleMap; String[] roleMapPairs = roleMapProp.split(","); for (String s : roleMapPairs) { String[] roleMapPair = s.split(":"); if (roleMapPair.length != 2) { throw new IllegalArgumentException( "Malformed rolemap property. Value must be a comma-separated list of values of the form <sakairole>:<ltirole>"); } roleMap.put(roleMapPair[0], roleMapPair[1]); } return roleMap; } }