Skip to content

Lti launch array parameters #34

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/main/java/org/imsglobal/lti/launch/LtiLaunch.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.imsglobal.lti.launch;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Map;

/**
Expand Down Expand Up @@ -38,6 +39,16 @@ public LtiLaunch(Map<String, String> parameters) {
this.toolConsumerInstanceGuid = parameters.get("tool_consumer_instance_guid");
}

public LtiLaunch(Collection<? extends Map.Entry> parameters) {
this.user = new LtiUser(parameters);
this.version = LtiOauthVerifier.getKey(parameters, "lti_version");
this.messageType = LtiOauthVerifier.getKey(parameters, "lti_message_type");
this.resourceLinkId = LtiOauthVerifier.getKey(parameters, "resource_link_id");
this.contextId = LtiOauthVerifier.getKey(parameters, "context_id");
this.launchPresentationReturnUrl = LtiOauthVerifier.getKey(parameters, "launch_presentation_return_url");
this.toolConsumerInstanceGuid = LtiOauthVerifier.getKey(parameters, "tool_consumer_instance_guid");
}

public LtiUser getUser() {
return user;
}
Expand Down
19 changes: 12 additions & 7 deletions src/main/java/org/imsglobal/lti/launch/LtiOauthSigner.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

Expand Down Expand Up @@ -64,17 +65,21 @@ public HttpRequest sign(HttpRequest request, String key, String secret) throws L

@Override
public Map<String, String> signParameters(Map<String, String> parameters, String key, String secret, String url, String method) throws LtiSigningException {
OAuthMessage oam = new OAuthMessage(method, url, parameters.entrySet());
Map<String, String> signedParameters = new HashMap<>();
for(Map.Entry<String, String> param : signParameters(parameters.entrySet(), key, secret, url, method)){
signedParameters.put(param.getKey(), param.getValue());
}
return signedParameters;
}

@Override
public Collection<Map.Entry<String, String>> signParameters(Collection<? extends Map.Entry<String, String>> parameters, String key, String secret, String url, String method) throws LtiSigningException {
OAuthMessage oam = new OAuthMessage(method, url, parameters);
OAuthConsumer cons = new OAuthConsumer(null, key, secret, null);
OAuthAccessor acc = new OAuthAccessor(cons);
try {
oam.addRequiredParameters(acc);

Map<String, String> signedParameters = new HashMap<>();
for(Map.Entry<String, String> param : oam.getParameters()){
signedParameters.put(param.getKey(), param.getValue());
}
return signedParameters;
return oam.getParameters();
} catch (OAuthException |IOException |URISyntaxException e) {
throw new LtiSigningException("Error signing LTI request.", e);
}
Expand Down
46 changes: 34 additions & 12 deletions src/main/java/org/imsglobal/lti/launch/LtiOauthVerifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
import net.oauth.server.OAuthServlet;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Map;
import java.util.*;
import java.util.logging.Logger;

/**
Expand All @@ -15,7 +14,7 @@
*/
public class LtiOauthVerifier implements LtiVerifier {

public static final String OAUTH_KEY_PARAMETER= "oauth_consumer_key";
public static final String OAUTH_KEY_PARAMETER = "oauth_consumer_key";

private final static Logger logger = Logger.getLogger(LtiOauthVerifier.class.getName());

Expand Down Expand Up @@ -60,16 +59,39 @@ public LtiVerificationResult verify(HttpServletRequest request, String secret) t
*/
@Override
public LtiVerificationResult verifyParameters(Map<String, String> parameters, String url, String method, String secret) throws LtiVerificationException {
OAuthMessage oam = new OAuthMessage(method, url, parameters.entrySet());
OAuthConsumer cons = new OAuthConsumer(null, parameters.get(OAUTH_KEY_PARAMETER), secret, null);
OAuthValidator oav = new SimpleOAuthValidator();
OAuthAccessor acc = new OAuthAccessor(cons);
return verifyParameters(parameters.entrySet(), url, method, secret);
}

try {
oav.validateMessage(oam, acc);
} catch (Exception e) {
return new LtiVerificationResult(false, LtiError.BAD_REQUEST, "Failed to validate: " + e.getLocalizedMessage() + ", Parameters: " + Arrays.toString(parameters.entrySet().toArray()));
@Override
public LtiVerificationResult verifyParameters(Collection<? extends Map.Entry<String, String>> parameters, String url, String method, String secret) throws LtiVerificationException {
OAuthMessage oam = new OAuthMessage(method, url, parameters);
String key = getKey(parameters, OAUTH_KEY_PARAMETER);
if(key == null) {
return new LtiVerificationResult(false, LtiError.BAD_REQUEST, "No key found in LTI request with parameters: " + Arrays.toString(parameters.toArray()));
} else {
OAuthConsumer cons = new OAuthConsumer(null, key, secret, null);
OAuthValidator oav = new SimpleOAuthValidator();
OAuthAccessor acc = new OAuthAccessor(cons);

try {
oav.validateMessage(oam, acc);
} catch (Exception e) {
return new LtiVerificationResult(false, LtiError.BAD_REQUEST, "Failed to validate: " + e.getLocalizedMessage() + ", Parameters: " + Arrays.toString(parameters.toArray()));
}
return new LtiVerificationResult(true, new LtiLaunch(parameters));
}
}

/**
* Given a collection of parameters, return the first value for the given key.
* returns null if no entry is found with the given key.
*/
public static String getKey(Collection<? extends Map.Entry> parameters, String parameterName) {
for(Map.Entry<String, String> entry: parameters) {
if(entry.getKey().equals(parameterName)) {
return entry.getValue();
}
}
return new LtiVerificationResult(true, new LtiLaunch(parameters));
return null;
}
}
16 changes: 16 additions & 0 deletions src/main/java/org/imsglobal/lti/launch/LtiSigner.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.apache.http.HttpRequest;

import java.util.Collection;
import java.util.Map;

/**
Expand Down Expand Up @@ -44,4 +45,19 @@ public interface LtiSigner {
*/
public Map<String, String> signParameters(Map<String, String> parameters, String key, String secret, String url, String method) throws LtiSigningException;

/**
* This method will return a list of <b>signed</b> parameters.
* Once returned, adding new parameters or changing the
* body will invalidate the signature. This method will
* overwrite reserved parameters from the underlying
* specification. For example, if you are using the Oauth
* implementation, <b>oauth_signature</b> will be removed
* &amp; replaced with the generated signature from the properties.
* @param parameters the parameters that will be signed. mapped by key &amp; value
* @param key the key that will be added to the request.
* @param secret the secret to be sign the parameters with
* @return a map of signed parameters (including the signature)
* @throws LtiSigningException
*/
public Collection<Map.Entry<String, String>> signParameters(Collection<? extends Map.Entry<String, String>> parameters, String key, String secret, String url, String method) throws LtiSigningException;
}
17 changes: 17 additions & 0 deletions src/main/java/org/imsglobal/lti/launch/LtiUser.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.imsglobal.lti.launch;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -33,6 +34,22 @@ public LtiUser(Map<String, String> parameters) {
}
}

public LtiUser(Collection<? extends Map.Entry> parameters) {
this.id = LtiOauthVerifier.getKey(parameters, "user_id");
this.roles = new LinkedList<>();
String parameterRoles = LtiOauthVerifier.getKey(parameters, "roles");
if(parameterRoles != null) {
for (String role : parameterRoles.split(",")) {
this.roles.add(role.trim());
}
}
}

public LtiUser(String id, List<String> roles) {
this.id = id;
this.roles = roles;
}

public String getId() {
return id;
}
Expand Down
37 changes: 32 additions & 5 deletions src/main/java/org/imsglobal/lti/launch/LtiVerifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Map;

/**
Expand All @@ -24,21 +25,47 @@ public interface LtiVerifier {
* information about the request).
* @throws LtiVerificationException
*/
public LtiVerificationResult verify(HttpServletRequest request, String secret) throws LtiVerificationException;
LtiVerificationResult verify(HttpServletRequest request, String secret) throws LtiVerificationException;

/**
* This method will verify a list of properties (mapped
* by key &amp; value).
* @param parameters the parameters that will be verified. mapped by key &amp; value
* @param url the url this request was made at
* @param parameters the parameters that will be verified. mapped by key &amp; value. This should only include parameters explicitly included in the body (not the url).


* @param parameters the parameters that will be verified. mapped by key &amp; value. This should include parameters explicitly included in the body
* (if this collection also includes parameters from the url, those parameters must be omitted in the `url` parameter to this method).
* The entries must be of type `Entry&lt;String,String&gt;`. If a specific key has multiple values (i.e. an array), each value must be in its own entry, each
* with the same key.
* @param url The url this request was made at. The url passed should be the same as sent for the request if the request includes parameters,
* they may be optionally included here, or in the collection passed to parameters.
* @param method the method this url was requested with
* @param secret the secret to verify the propertihes with
* @param secret the secret to verify the properties with
* @return an LtiVerificationResult which will
* contain information about the request (whether or
* not it is valid, and if it is valid, contextual
* information about the request).
* @throws LtiVerificationException
*/
LtiVerificationResult verifyParameters(Map<String, String> parameters, String url, String method, String secret) throws LtiVerificationException;

/**
* This method will verify a list of properties (mapped
* by key &amp; value).
* @param parameters the parameters that will be verified. mapped by key &amp; value. This should include parameters explicitly included in the body
* (if this collection also includes parameters from the url, those parameters must be omitted in the `url` parameter to this method).
* The entries must be of type `Entry&lt;String,String&gt;`. If a specific key has multiple values (i.e. an array), each value must be in its own entry, each
* with the same key.
* @param url The url this request was made at. The url passed should be the same as sent for the request if the request includes parameters,
* they may be optionally included here, or in the collection passed to parameters.
* @param method the method this url was requested with
* @param secret the secret to verify the properties with
* @return an LtiVerificationResult which will
* contain information about the request (whether or
* not it is valid, and if it is valid, contextual
* information about the request).
* @throws LtiVerificationException
*/
public LtiVerificationResult verifyParameters(Map<String, String> parameters, String url, String method, String secret) throws LtiVerificationException;
LtiVerificationResult verifyParameters(Collection<? extends Map.Entry<String, String>> parameters, String url, String method, String secret) throws LtiVerificationException;

}
77 changes: 77 additions & 0 deletions src/test/java/org/imsglobal/lti/launch/LtiOauthVerifierTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package org.imsglobal.lti.launch;

import org.apache.http.client.methods.HttpPost;
import org.codehaus.jackson.map.ser.std.IterableSerializer;
import org.junit.Test;

import java.net.URI;
import java.util.*;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

/**
* Created by paul on 9/27/17.
*/
public class LtiOauthVerifierTest {


LtiVerifier verifier = new LtiOauthVerifier();
LtiSigner signer = new LtiOauthSigner();


@Test
public void verifierShouldAllowUrlParametersInUrlParam() throws Exception {

String baseUrl = "http://example.com/test";
String urlParams = "?bestLanguage=java";

String key = "key";
String secret = "secret";

Collection<AbstractMap.SimpleEntry<String, String>> myEntries = Arrays.asList(
new AbstractMap.SimpleEntry<String, String>("hm", "okasy"),
new AbstractMap.SimpleEntry<String, String>("hm", "asdf"),
new AbstractMap.SimpleEntry<String, String>("wat", "asdfasdff")
);

Collection signedParameters = signer.signParameters(myEntries, key, secret, baseUrl + urlParams, "GET");


// including the parameters in the url should yield success
LtiVerificationResult successResult = verifier.verifyParameters(signedParameters, baseUrl + urlParams, "GET", secret);
assertTrue(successResult.getSuccess());

// omitting the parameters in the url should yield failure
LtiVerificationResult failResult = verifier.verifyParameters(signedParameters, baseUrl, "GET", secret);
assertFalse(failResult.getSuccess());
}

@Test
public void verifierShouldAllowUrlParametersInEntryMap() throws Exception {

String baseUrl = "http://example.com/test";
String urlParams = "?bestLanguage=java";

String key = "key";
String secret = "secret";

List<AbstractMap.SimpleEntry<String, String>> myEntries = Arrays.asList(
new AbstractMap.SimpleEntry<String, String>("hm", "okasy"),
new AbstractMap.SimpleEntry<String, String>("hm", "asdf"),
new AbstractMap.SimpleEntry<String, String>("wat", "asdfasdff")
);

Collection signedParameters = signer.signParameters(myEntries, key, secret, baseUrl + urlParams, "GET");

List signedParamsWithUrlParams = new ArrayList<>(signedParameters);
signedParamsWithUrlParams.add(new AbstractMap.SimpleEntry<String, String>("bestLanguage", "java"));
// including the url parameters in the entries map should yield success
LtiVerificationResult successResult = verifier.verifyParameters(signedParamsWithUrlParams, baseUrl, "GET", secret);
assertTrue(successResult.getSuccess());

// omitting the url parameters in the entries map and omitting them in the url should yield failure
LtiVerificationResult failResult = verifier.verifyParameters(signedParameters, baseUrl, "GET", secret);
assertFalse(failResult.getSuccess());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import static org.junit.Assert.*;
import java.net.URI;
import java.util.*;


/**
Expand Down Expand Up @@ -57,17 +58,4 @@ public void verifierShouldVerifyCorrectlySignedLtiGetServiceRequests() throws Ex
assertTrue(result.getSuccess());
}

@Test
public void verifierShouldRejectIncorrectlySignedLtiGetServiceRequests() throws Exception {

String key = "key";
String secret = "secret";
HttpGet ltiServiceGetRequest = new HttpGet(new URI("http://example.com/test"));

signer.sign(ltiServiceGetRequest, key, secret);
LtiVerificationResult result = verifier.verify(new MockHttpGet(ltiServiceGetRequest), "anotherWrongSecret");

assertFalse(result.getSuccess());
}

}