Skip to content

Commit

Permalink
Merge pull request sakaiproject#731 from csev/SAK-29562
Browse files Browse the repository at this point in the history
SAK-29562 - Add support for the SHA-256 signing as described in LTI 2.1
  • Loading branch information
csev committed Jun 24, 2015
2 parents 54037a7 + ed02dfc commit 9ce7b61
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 49 deletions.
2 changes: 2 additions & 0 deletions basiclti/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
tsugi-sakai
tsugi-java-servlet
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ public interface LTIService {
"xmlimport:text:hidden=true:maxlength=16384",
// LTI 2.x settings
"settings:text:hidden=true:maxlength=8096",
"enabled_capability:text:hidden=true:maxlength=16384:role=admin",
// Sakai LTI 1.x extension settings (see SAK-25621)
"settings_ext:text:hidden=true:maxlength=8096",
"placement:text:hidden=true:maxlength=256",
Expand Down Expand Up @@ -563,7 +564,7 @@ public interface LTIService {
"settings:text:hidden=true:maxlength=8096",
// LTI 2.x tool-registration time parameters
"parameter:textarea:label=bl_parameter:rows=5:cols=25:maxlength=1024:only=lti2",
"enabled_capability:textarea:label=bl_enabled_capability:rows=5:cols=25:maxlength=1024:only=lti2",
"enabled_capability:textarea:label=bl_enabled_capability:rows=5:cols=25:maxlength=16384:only=lti2",
"allowcustom:checkbox:label=bl_allowcustom",
"xmlimport:text:hidden=true:maxlength=16384",
"splash:textarea:label=bl_splash:rows=5:cols=25:maxlength=4096",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,23 +220,23 @@ protected ToolConsumer getToolConsumerProfile(Map<String, Object> deploy, String
String serverUrl = SakaiBLTIUtil.getOurServerUrl();

ToolConsumer consumer = new ToolConsumer(profile_id+"", resourceUrl, cnf);
List<String> capabilities = consumer.getCapability_offered();
LTI2Util.allowSplitSecret(capabilities);
consumer.allowSplitSecret();
consumer.allowHmac256();

if (foorm.getLong(deploy.get(LTIService.LTI_SENDEMAILADDR)) > 0 ) {
LTI2Util.allowEmail(capabilities);
consumer.allowEmail();
}

if (foorm.getLong(deploy.get(LTIService.LTI_SENDNAME)) > 0 ) {
LTI2Util.allowName(capabilities);
consumer.allowName();
}

List<Service_offered> services = consumer.getService_offered();
services.add(StandardServices.LTI2Registration(serverUrl + LTI2_PATH + SVC_tc_registration + "/" + profile_id));

String allowOutcomes = ServerConfigurationService.getString(SakaiBLTIUtil.BASICLTI_OUTCOMES_ENABLED, SakaiBLTIUtil.BASICLTI_OUTCOMES_ENABLED_DEFAULT);
if ("true".equals(allowOutcomes) && foorm.getLong(deploy.get(LTIService.LTI_ALLOWOUTCOMES)) > 0 ) {
LTI2Util.allowResult(capabilities);
consumer.allowResult();

services.add(LTI2ResultItem);
services.add(StandardServices.LTI1Outcomes(serverUrl+LTI1_PATH));
Expand All @@ -250,7 +250,7 @@ protected ToolConsumer getToolConsumerProfile(Map<String, Object> deploy, String

String allowSettings = ServerConfigurationService.getString(SakaiBLTIUtil.BASICLTI_SETTINGS_ENABLED, SakaiBLTIUtil.BASICLTI_SETTINGS_ENABLED_DEFAULT);
if ("true".equals(allowSettings) && foorm.getLong(deploy.get(LTIService.LTI_ALLOWSETTINGS)) > 0 ) {
LTI2Util.allowSettings(capabilities);
consumer.allowSettings();

services.add(SakaiLTI2Services.BasicSettings(serverUrl+LTI1_PATH));
services.add(LTI2LtiLinkSettings);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

import org.imsglobal.lti2.LTI2Constants;
import org.imsglobal.lti2.LTI2Util;
import org.imsglobal.lti2.objects.ToolConsumer;

import org.sakaiproject.lti.api.LTIService;

Expand Down Expand Up @@ -78,6 +79,7 @@
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;
Expand Down Expand Up @@ -823,6 +825,17 @@ public static String[] postLaunchHTML(Map<String, Object> content, Map<String,Ob
// Place the custom values into the launch
LTI2Util.addCustomToLaunch(ltiProps, custom);

// Check which kind of signing we are supposed to do
String enabled_capability = (String) content.get("enabled_capability");
if ( enabled_capability == null ) {
enabled_capability = (String) tool.get("enabled_capability");
}

if ( LTI2Util.enabledCapability(enabled_capability, ToolConsumer.OAUTH_HMAC256) ) {
ltiProps.put(OAuth.OAUTH_SIGNATURE_METHOD,"HMAC-SHA256");
M_log.debug("Launching with SHA256 Signing");
}

return postLaunchHTML(toolProps, ltiProps, rb);
}

Expand Down Expand Up @@ -1006,7 +1019,7 @@ public static String[] postLaunchHTML(Properties toolProps, Properties ltiProps,
return postError("<p>" + getRB(rb, "error.nokey", "Error - must have a secret and a key.")+"</p>");
}

Map<String,String> extra = new HashMap<String,String> ();
Map<String,String> extra = new HashMap<String,String> ();
ltiProps = BasicLTIUtil.signProperties(ltiProps, launch_url, "POST",
key, secret, org_guid, org_desc, org_url, extra);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@
"Result.sourcedId" ,
"Result.autocreate",
"Result.custom.url",
"OAuth.splitSecret"
"OAuth.splitSecret",
"OAuth.hmac-sha256"
],
"service_offered": [
{
Expand Down
7 changes: 7 additions & 0 deletions basiclti/basiclti-docs/resources/docs/sakai-api-test/tp.php
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,13 @@ function dataToggle(divName) {

// Make a split-secret if desired
$oauth_splitsecret = in_array('OAuth.splitSecret', $tc_capabilities);

// We don't do oauth_split secret here because we have no storage
// You can test split secret with this harness but launches will fail.
// Comment out the line below to make it so this registers with split secret
// But then expect LTI 2.x launches to fail with a bad signature.
$oauth_splitsecret = false;

$tp_half_secret = false;
if ( $oauth_splitsecret ) {
$tp_half_secret = bin2hex( openssl_random_pseudo_bytes( 512/8 ) ) ;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,11 +326,13 @@ protected ToolConsumer buildToolConsumerProfile(HttpServletRequest request, Map<
ToolConsumer consumer = new ToolConsumer(profile_id+"", getServiceURL(request), cnf);

// Normally we would check permissions before we offer capabilities
List<String> capabilities = consumer.getCapability_offered();
LTI2Util.allowEmail(capabilities);
LTI2Util.allowName(capabilities);
LTI2Util.allowSettings(capabilities);
LTI2Util.allowResult(capabilities);
consumer.allowEmail();
consumer.allowName();
consumer.allowSettings();
consumer.allowResult();
// Keep our life simple
// consumer.allowSplitSecret();
// consumer.allowHmac256();

// Normally we would check permissions before we offer services
List<Service_offered> services = consumer.getService_offered();
Expand Down
47 changes: 16 additions & 31 deletions basiclti/basiclti-util/src/java/org/imsglobal/lti2/LTI2Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public static String validateServices(ToolConsumer consumer, JSONObject provider
}
}

// Validate incoming capabilities requested against out ToolConsumer
// Validate incoming capabilities requested against our ToolConsumer
public static String validateCapabilities(ToolConsumer consumer, JSONObject providerProfile)
{
List<Properties> theTools = new ArrayList<Properties> ();
Expand Down Expand Up @@ -113,35 +113,6 @@ public static String validateCapabilities(ToolConsumer consumer, JSONObject prov
}
}

public static void allowEmail(List<String> capabilities) {
capabilities.add("Person.email.primary");
}

public static void allowName(List<String> capabilities) {
capabilities.add("User.username");
capabilities.add("Person.name.fullname");
capabilities.add("Person.name.given");
capabilities.add("Person.name.family");
capabilities.add("Person.name.full");
}

public static void allowResult(List<String> capabilities) {
capabilities.add("Result.sourcedId");
capabilities.add("Result.autocreate");
capabilities.add("Result.url");
}

public static void allowSettings(List<String> capabilities) {
capabilities.add("LtiLink.custom.url");
capabilities.add("ToolProxy.custom.url");
capabilities.add("ToolProxyBinding.custom.url");
}

public static void allowSplitSecret(List<String> capabilities) {
capabilities.add("OAuth.splitSecret");
}


// If this code looks like a hack - it is because the spec is a hack.
// There are five possible scenarios for GET and two possible scenarios
// for PUT. I begged to simplify the business logic but was overrulled.
Expand Down Expand Up @@ -439,13 +410,27 @@ private static String parseToolProfileInternal(List<Properties> theTools, Proper
if ( resourceDescription == null ) resourceDescription = productDescription;
if ( resourceDescription != null ) theTool.put("description", resourceDescription);
if ( parameter != null ) theTool.put("parameter", parameter.toString());
if ( enabled_capability != null ) theTool.put("enabled_capability", enabled_capability.toString());
theTool.put("enabled_capability", enabled_capability.toString());
theTool.put("launch", thisLaunch);
theTools.add(theTool);
}
return null; // All good
}

/**
* Check if a particular capability is enabled
*
* @param String enabledCapabilityString - The string representation of the JSON
* array of capabilities.
* @param String capability - The capability to look for
*/
public static boolean enabledCapability(String enabledCapabilityString, String capability)
{
if ( enabledCapabilityString == null || capability == null ) return false;
// For now just look for the string in quotes.
return enabledCapabilityString.indexOf("\""+capability+"\"") >= 0 ;
}

public static JSONObject parseSettings(String settings)
{
if ( settings == null || settings.length() < 1 ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,32 @@
})
public class ToolConsumer {

public static final String LTI_LAUNCH = "basic-lti-launch-request";

public static final String USER_ID = "User.id";
public static final String USER_USERNAME = "User.username";
public static final String USER_IMAGE = "User.image" ;

public static final String COURSESECTION_SOURCEDID = "CourseSection.sourcedId";
public static final String MEMBERSHIP_ROLE = "Membership.role";

public static final String PERSON_SOURCEDID = "Person.sourcedId";
public static final String PERSON_NAME_FULL = "Person.name.full";
public static final String PERSON_NAME_GIVEN = "Person.name.given" ;
public static final String PERSON_NAME_FAMILY = "Person.name.family" ;
public static final String PERSON_EMAIL_PRIMARY = "Person.email.primary" ;

public static final String LTILINK_CUSTOM_URL = "LtiLink.custom.url";
public static final String TOOLPROXY_CUSTOM_URL = "ToolProxy.custom.url";
public static final String TOOLPROXYBINDING_CUSTOM_URL = "ToolProxyBinding.custom.url";

public static final String RESULT_SOURCEDID = "Result.sourcedId" ;
public static final String RESULT_AUTOCREATE = "Result.autocreate";
public static final String RESULT_URL = "Result.url";

public static final String OAUTH_SPLITSECRET = "OAuth.splitSecret";
public static final String OAUTH_HMAC256 = "OAuth.hmac-sha256";

@JsonProperty("@context")
private List<Object> _context = new ArrayList<Object>();
@JsonProperty("@type")
Expand All @@ -50,15 +76,15 @@ public class ToolConsumer {
private Map<String, Object> additionalProperties = new HashMap<String, Object>();

public static String[] STANDARD_CAPABILITIES = {
"basic-lti-launch-request" , "User.id", "User.image" ,
"CourseSection.sourcedId", "Person.sourcedId", "Membership.role"
LTI_LAUNCH, USER_ID, COURSESECTION_SOURCEDID,
PERSON_SOURCEDID, MEMBERSHIP_ROLE
} ;

// Constructor
public ToolConsumer(String guid, String tcp, LTI2Config cnf) {
this._context.add("http://purl.imsglobal.org/ctx/lti/v2/ToolConsumerProfile");
NamedContext nc = new NamedContext();
nc.setAdditionalProperties("tcp", tcp);
NamedContext nc = new NamedContext();
nc.setAdditionalProperties("tcp", tcp);
this._context.add(nc);
this._type = "ToolConsumerProfile";
this.lti_version = "LTI-2p0";
Expand Down Expand Up @@ -162,4 +188,40 @@ public void addCapabilites(String [] capabilities) {
Collections.addAll(this.capability_offered, capabilities);
}

// Convienence method
public void addCapability(String capability) {
this.capability_offered.add(capability);
}

public void allowEmail() {
this.capability_offered.add(PERSON_EMAIL_PRIMARY);
}

public void allowName() {
this.capability_offered.add(USER_USERNAME);
this.capability_offered.add(PERSON_NAME_FULL);
this.capability_offered.add(PERSON_NAME_GIVEN);
this.capability_offered.add(PERSON_NAME_FAMILY);
this.capability_offered.add(PERSON_NAME_FULL);
}

public void allowResult() {
this.capability_offered.add(RESULT_SOURCEDID);
this.capability_offered.add(RESULT_AUTOCREATE);
this.capability_offered.add(RESULT_URL);
}

public void allowSettings() {
this.capability_offered.add(LTILINK_CUSTOM_URL);
this.capability_offered.add(TOOLPROXY_CUSTOM_URL);
this.capability_offered.add(TOOLPROXYBINDING_CUSTOM_URL);
}

public void allowSplitSecret() {
this.capability_offered.add(OAUTH_SPLITSECRET);
}

public void allowHmac256() {
this.capability_offered.add(OAUTH_HMAC256);
}
}

0 comments on commit 9ce7b61

Please sign in to comment.