Browse Source

Merge branch 'feature/picker' of gremlin/lti-demo-cmr into develop

gremlin 4 years ago
parent
commit
b24b0acbc5

+ 7 - 0
pom.xml

@@ -84,6 +84,13 @@
 			<artifactId>spring-integration-test</artifactId>
 			<scope>test</scope>
 		</dependency>
+		<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk14 -->
+		<dependency>
+			<groupId>org.bouncycastle</groupId>
+			<artifactId>bcprov-jdk14</artifactId>
+			<version>1.68</version>
+		</dependency>
+
 	</dependencies>
 
 	<build>

+ 3 - 1
src/main/java/riomhaire/lti/JavaLti13ToolApplication.java

@@ -43,6 +43,7 @@ public class JavaLti13ToolApplication extends WebMvcConfigurerAdapter {
      * @param args command line arguments
      */
     public static void main(String[] args) {
+        java.security.Security.addProvider( new org.bouncycastle.jce.provider.BouncyCastleProvider() );
         SpringApplication.run(JavaLti13ToolApplication.class, args);
     }
 
@@ -86,7 +87,8 @@ public class JavaLti13ToolApplication extends WebMvcConfigurerAdapter {
                 .addCommand(LTIMessageType.ProcessLTIMessage.name(), new LTILaunchCommand())
                 .addCommand(LTIMessageType.LaunchContent.name(), new LaunchContentCommand())
                 .addCommand(LTIMessageType.BuildJWKSResponse.name(), new BuildJWKSResponseCommand())
-                .addCommand(LTIMessageType.SelectDeepLinkContent.name(), new LaunchCreateDeepLink());
+                .addCommand(LTIMessageType.SelectDeepLinkContent.name(), new LaunchCreateDeepLinkCommand())
+                .addCommand(LTIMessageType.ProcessStoreDeepLink.name(),new StoreDeepLinkInLMSCommand());
 
     }
 

+ 1 - 1
src/main/java/riomhaire/lti/core/business/LTIMessageDispatcher.java

@@ -44,7 +44,7 @@ public class LTIMessageDispatcher implements CommandDispatcher<ModelAndView, App
      */
     @Override
     public ModelAndView dispatch(ApplicationRegistry<ModelAndView,LTIMessage> registry, LTIMessage message) {
-        var mav = new ModelAndView("nop");
+        var mav = new ModelAndView("dump-launch");
         // If we have a handler for this command execute it
         if (message != null && commands.containsKey(message.getMessageType())) {
             mav = commands.get(message.getMessageType()).execute(registry, message);

+ 2 - 2
src/main/java/riomhaire/lti/core/business/commands/BuildJWKSResponseCommand.java

@@ -40,7 +40,7 @@ public class BuildJWKSResponseCommand  implements Command<ModelAndView, Applicat
         ModelAndView mav;
         var objectMapper = new ObjectMapper();
         try {
-            // Step 1 - retrieve config
+            // Step 1 - retrieve out tool config
             var optionalConfiguration =  environment.toolConfigurationResolver().lookup();
             if( optionalConfiguration.isEmpty() ) throw new FileNotFoundException("No Tool Configuration has been set");
 
@@ -57,7 +57,7 @@ public class BuildJWKSResponseCommand  implements Command<ModelAndView, Applicat
                 rsaJsonWebKey.setKeyId(configKeyEntry.getKeyId());
                 webKeys.add(rsaJsonWebKey);
             }
-            // Conveluted  hack
+            // Convoluted  hack
             var jwks = new JsonWebKeySet(webKeys);
             var raw = objectMapper.readValue(jwks.toJson(JsonWebKey.OutputControlLevel.PUBLIC_ONLY), HashMap.class);
             // Step 3 - render as json response

+ 1 - 1
src/main/java/riomhaire/lti/core/business/commands/LTILaunchCommand.java

@@ -34,7 +34,7 @@ public class LTILaunchCommand implements Command<ModelAndView, ApplicationRegist
         var idToken = message.getQueryParams().get("id_token");
         var jwtToken = message.getQueryParams().get("JWT");
         // Crack open so we can find the config
-        String token = (idToken != null) ? idToken : jwtToken;
+        var token = (idToken != null) ? idToken : jwtToken;
         var jwt = JWT.decode(token);
         // Lookup client id and issuer
         var issuer = jwt.getClaim("iss").asString();

+ 3 - 2
src/main/java/riomhaire/lti/core/business/commands/LaunchCreateDeepLink.java → src/main/java/riomhaire/lti/core/business/commands/LaunchCreateDeepLinkCommand.java

@@ -8,13 +8,13 @@ import riomhaire.lti.core.model.interfaces.Command;
 import java.util.HashMap;
 import java.util.function.Function;
 
-public class LaunchCreateDeepLink  implements Command<ModelAndView, ApplicationRegistry<ModelAndView, LTIMessage>, LTIMessage> {
+public class LaunchCreateDeepLinkCommand implements Command<ModelAndView, ApplicationRegistry<ModelAndView, LTIMessage>, LTIMessage> {
 
     @Override
     public ModelAndView execute(ApplicationRegistry<ModelAndView,LTIMessage> environment, LTIMessage message) {
         final String prefix = "https://purl.imsglobal.org/spec/lti/claim/";
         var simplifiedClaims = new HashMap<String, Object>();
-        var mav = new ModelAndView("deep-link-selection");
+        var mav = new ModelAndView("select-deep-link");
 //        var mav = new ModelAndView("redirect:https://www.riomhaire.com");
 
         // Strip "https://purl.imsglobal.org/spec/lti/claim/" from keys
@@ -27,6 +27,7 @@ public class LaunchCreateDeepLink  implements Command<ModelAndView, ApplicationR
         message.getClaims().forEach((s, o) -> simplifiedClaims.put(simplifier.apply(s), o));
         mav.addObject("claims", simplifiedClaims);
         mav.addObject("metadata", message.getMetadata());
+        mav.addObject("queryParameters", message.getQueryParams());
 
         return mav;
     }

+ 189 - 0
src/main/java/riomhaire/lti/core/business/commands/StoreDeepLinkInLMSCommand.java

@@ -0,0 +1,189 @@
+package riomhaire.lti.core.business.commands;
+
+
+import com.auth0.jwt.JWT;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.jose4j.jwk.PublicJsonWebKey;
+import org.jose4j.jwk.RsaJsonWebKey;
+import org.jose4j.jws.AlgorithmIdentifiers;
+import org.jose4j.jws.JsonWebSignature;
+import org.jose4j.keys.RsaKeyUtil;
+import org.jose4j.lang.JoseException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.servlet.ModelAndView;
+import riomhaire.lti.core.model.LTIMessage;
+import riomhaire.lti.core.model.interfaces.ApplicationRegistry;
+import riomhaire.lti.core.model.interfaces.Command;
+
+import java.io.DataOutputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.*;
+
+public class StoreDeepLinkInLMSCommand implements Command<ModelAndView, ApplicationRegistry<ModelAndView, LTIMessage>, LTIMessage> {
+
+        public static final String LTI_CLAIM_DEPLOYMENT_ID = "https://purl.imsglobal.org/spec/lti/claim/deployment_id";
+        public static final String LTI_CLAIM_MESSAGE_TYPE = "https://purl.imsglobal.org/spec/lti/claim/message_type";
+        public static final String LTI_CLAIM_VERSION = "https://purl.imsglobal.org/spec/lti/claim/version";
+        public static final String LTI_DL_CLAIM_CONTENT_ITEMS = "https://purl.imsglobal.org/spec/lti-dl/claim/content_items";
+        public static final String LTI_CLAIM_TARGET_LINK_URI = "https://purl.imsglobal.org/spec/lti/claim/target_link_uri";
+        public static final String LTI_DL_CLAIM_DEEP_LINKING_SETTINGS = "https://purl.imsglobal.org/spec/lti-dl/claim/deep_linking_settings";
+        public static final String LTI_DEEP_LINK_RETURN_URL = "deep_link_return_url";
+        // Logger
+        private Logger log = LoggerFactory.getLogger(this.getClass());
+
+        @Override
+public ModelAndView execute(ApplicationRegistry<ModelAndView,LTIMessage> environment, LTIMessage message) {
+        var signedDLJwt="";
+
+        // OK steps are
+        // 1 - decode originating token
+        var token = message.getQueryParams().get("id_token");
+        var jwt = JWT.decode(token);
+        
+        // Lookup client id and issuer
+        var issuer = jwt.getClaim("iss").asString();
+        var audience = jwt.getClaim("aud").asString();
+
+        // 2 - build response structure
+        var deepLinkResponse = new HashMap<String,Object>();
+        // Common stuff
+        deepLinkResponse.put("aud",issuer);
+        deepLinkResponse.put("iss", audience);
+
+        deepLinkResponse.put(LTI_CLAIM_MESSAGE_TYPE,"LtiDeepLinkingRequest");
+        deepLinkResponse.put(LTI_CLAIM_DEPLOYMENT_ID,jwt.getClaim(LTI_CLAIM_DEPLOYMENT_ID).asString());
+        deepLinkResponse.put(LTI_CLAIM_VERSION,"1.3.0");
+        deepLinkResponse.put("iat", Instant.now().getEpochSecond());
+        deepLinkResponse.put("exp", Instant.now().plus(1, ChronoUnit.HOURS).getEpochSecond());
+        deepLinkResponse.put("nonce", UUID.randomUUID().toString());
+        // Add the actual item
+        var contentItems = new ArrayList<Map<String,Object>>();
+        var contentLink = new HashMap<String,Object>();
+        var customParameters = new HashMap<String,Object>();
+        customParameters.put("resource_url",message.getQueryParams().get("url"));
+
+        contentLink.put("type","ltiResourceLink");
+        contentLink.put("title",message.getQueryParams().get("title"));
+        contentLink.put("url",jwt.getClaim(LTI_CLAIM_TARGET_LINK_URI).asString());
+        contentLink.put("custom",customParameters);
+
+        contentItems.add(contentLink);
+        deepLinkResponse.put(LTI_DL_CLAIM_CONTENT_ITEMS,contentItems);
+
+        // 3 - sign response structure using our private key
+        var optionalConfiguration =  environment.toolConfigurationResolver().lookup();
+        if( !optionalConfiguration.isEmpty() ) {
+                // 4 - post a response to LMS      (or set up for self posting form)
+//        var targetUrl =
+                try {
+                        signedDLJwt = signPayloadUsingOurPrivateKey(deepLinkResponse, optionalConfiguration);
+                } catch (Exception  e) {
+                        // Todo handle this
+                   log.error(e.getMessage(),e);
+                }
+
+
+        }
+
+        // 5 - display 'success' page (maybe inc self post form)
+        var lmsDeepLinkClaim = jwt.getClaim(LTI_DL_CLAIM_DEEP_LINKING_SETTINGS);
+        var lmsTargetUrl = lmsDeepLinkClaim.asMap().get(LTI_DEEP_LINK_RETURN_URL).toString();
+        var state = message.getQueryParams().get("state").toString();
+        var mav = new ModelAndView("store-deep-link-in-lms");
+        mav.addObject("targetURL",lmsTargetUrl);
+        mav.addObject("state",state);
+        mav.addObject("JWT",signedDLJwt);
+        
+        return mav;
+   }
+
+
+        /**
+         * This method signs a series of claims using our public key
+         * @param deepLinkResponse
+         * @param optionalConfiguration
+         * @return
+         * @throws JsonProcessingException
+         * @throws JoseException
+         * @throws InvalidKeySpecException
+         */
+        private String signPayloadUsingOurPrivateKey(HashMap<String, Object> deepLinkResponse, Optional<riomhaire.lti.core.model.configuration.ToolConfiguration> optionalConfiguration) throws JsonProcessingException, JoseException, InvalidKeySpecException, NoSuchAlgorithmException {
+                JsonWebSignature jws = new JsonWebSignature();
+
+                // The payload of the JWS is JSON content of the JWT Claims
+                ObjectMapper serializer = new ObjectMapper();
+                String json = serializer.writerWithDefaultPrettyPrinter().writeValueAsString(deepLinkResponse);
+
+
+
+                jws.setPayload(json);
+
+                // The JWT is signed using the private key - we will use the 1st in the list
+                var jwkKey = optionalConfiguration.get().getJwkKeys()[0];
+
+                var privatePemString =  new String(Base64.getDecoder().decode(jwkKey.getPrivateKey()));   // default to 1st
+                var publicPemString =  new String(Base64.getDecoder().decode(jwkKey.getPublicKey()));   // default to 1st
+                var rsaJsonWebKey = (RsaJsonWebKey) PublicJsonWebKey.Factory.newPublicJwk(new RsaKeyUtil().fromPemEncoded(publicPemString));
+                // So target will know which to verify against
+                rsaJsonWebKey.setKeyId(jwkKey.getKeyId());
+                // Add private key  after striping headers
+                privatePemString = privatePemString.replace("-----BEGIN RSA PRIVATE KEY-----", "");
+                privatePemString = privatePemString.replace("-----END RSA PRIVATE KEY-----", "");
+                privatePemString = privatePemString.replaceAll("\\s+","");
+
+                var keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privatePemString));
+                var keyFactory = KeyFactory.getInstance("RSA");
+                var privateKey = keyFactory.generatePrivate(keySpec); // error?
+
+                jws.setKey(privateKey);
+                jws.setKeyIdHeaderValue(jwkKey.getKeyId());
+                jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
+
+                // Sign the JWS and produce the compact serialization or the complete JWT/JWS
+                var signedJWT = jws.getCompactSerialization();
+                return signedJWT;
+        }
+
+
+
+//        private void postToLMS(String lmsTargetUrl, String signedDLJwt, String state) {
+//                try {
+//                        URL url = new URL(lmsTargetUrl);
+//                        URLConnection con = null;
+//                        con = url.openConnection();
+//                        HttpURLConnection http = (HttpURLConnection)con;
+//                        http.setRequestMethod("POST"); // PUT is another valid option
+//                        http.setDoOutput(true);
+//                        StringBuilder formParams = new StringBuilder();
+//                        formParams.append("JWT=");
+//                        formParams.append(signedDLJwt);
+//                        formParams.append("&state=");
+//                        formParams.append(state);
+//
+//                        byte[] out = formParams.toString().getBytes(StandardCharsets.UTF_8);
+//                        int length = formParams.length();
+//                        http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
+//                        http.setRequestProperty("Content-Length", Integer.toString(length ));
+//                        http.setUseCaches(false);
+//                        http.connect();
+//                        try(DataOutputStream wr = new DataOutputStream(http.getOutputStream())) {
+//                                wr.write( out );
+//                        }
+//                } catch (Throwable e) {
+//                        e.printStackTrace();
+//                }
+//        }
+
+}

+ 29 - 0
src/main/java/riomhaire/lti/core/infrastructure/api/internal/CallbackEndpoint.java

@@ -0,0 +1,29 @@
+package riomhaire.lti.core.infrastructure.api.internal;
+
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.ModelAndView;
+import riomhaire.lti.core.infrastructure.api.Endpoint;
+import riomhaire.lti.core.model.LTIMessage;
+import riomhaire.lti.core.model.LTIMessageType;
+
+import java.util.Map;
+
+@RestController
+public class CallbackEndpoint  extends Endpoint {
+
+
+    @RequestMapping(path = "/callback/storedeeplink")
+    public ModelAndView callback(@RequestParam Map<String, String> queryParams, @RequestHeader Map<String, Object> headers) {
+
+        var message = LTIMessage.builder()
+                .messageType(LTIMessageType.ProcessStoreDeepLink.name())
+                .metadata(headers)
+                .queryParams(queryParams)
+                .build();
+        return getDispatcher().dispatch(getRegistry(), message);
+    }
+
+}

+ 1 - 0
src/main/java/riomhaire/lti/core/model/LTIMessageType.java

@@ -5,5 +5,6 @@ public enum LTIMessageType {
     ProcessLTIMessage,
     ProcessOidcRequest,
     SelectDeepLinkContent,
+    ProcessStoreDeepLink,
     BuildJWKSResponse
 }

+ 2 - 2
src/main/resources/templates/deep-link-selection.html → src/main/resources/static/teachers.html

@@ -2,7 +2,7 @@
 <html xmlns="http://www.w3.org/1999/xhtml"  xmlns:th="http://www.thymeleaf.org">
 
 <head>
-    <title>Learn Center | Deep Link Selection</title>
+    <title>Learn Center | Content</title>
     <link rel="shortcut icon" type="image/png" href="/img/logo-small.png"/>
     <meta charset="utf-8">
     <link rel="stylesheet" href="/css/reset.css" type="text/css" media="all">
@@ -55,7 +55,7 @@
                 <div class="wrapper">
                     <article class="col1">
                         <div class="pad_left1">
-                            <h2>Deep Link Selection</h2>
+                            <h2>Launch Payload</h2>
                         </div>
                         <div>
                             <h3>Meta Data</h3>

+ 27 - 4
src/main/resources/templates/dump-launch.html

@@ -58,16 +58,39 @@
                             <h2>Launch Payload</h2>
                         </div>
                         <div>
-                            <h3>Meta Data</h3>
-                            <table class="demo-table">
+                            <h3 th:if="${metadata != null}">Meta Data</h3>
+                            <table class="demo-table" th:if="${metadata != null}">
                                 <tr th:each="metadata : ${metadata}">
                                     <th th:text="${metadata.key}">keyvalue</th>
                                     <td th:text="${metadata.value}">num</td>
                                 </tr>
                             </table>
 
-                            <h3>Launch Claims</h3>
-                            <table title="Launch Claims"  class="demo-table">
+                            <h3 th:if="${queryParameters != null}">Query Parameters</h3>
+                            <table title="Launch Parameters"  class="demo-table"  th:if="${queryParameters != null}">
+                                <tr th:each="claim : ${queryParameters}">
+                                    <th th:text="${claim.key}"></th>
+                                    <td th:if="${claim.value.class.name == 'java.util.ArrayList'}">
+                                        <div th:each="row : ${claim.value}">
+                                            <div th:text="${row}"/>
+                                        </div>
+                                    </td>
+                                    <td th:if="${claim.value.class.name == 'org.jose4j.json.JsonUtil$DupeKeyDisallowingLinkedHashMap'}">
+                                        <table>
+                                            <tr th:each="objmap : ${claim.value}">
+                                                <th th:text="${objmap.key}"></th>
+                                                <td th:text="${objmap.value}"></td>
+
+                                            </tr>
+                                        </table>
+                                    </td>
+                                    <td th:if="${claim.value.class.name == 'java.lang.String'}" th:text="${claim.value}">num</td>
+                                    <td th:if="${claim.value.class.name == 'java.lang.Long'}" th:text="${claim.value}">num</td>
+                                </tr>
+                            </table>
+
+                            <h3  th:if="${claims != null}">Launch Claims</h3>
+                            <table title="Launch Claims"  class="demo-table"  th:if="${claims != null}">
                                 <tr th:each="claim : ${claims}">
                                     <th th:text="${claim.key}"></th>
                                     <td th:if="${claim.value.class.name == 'java.util.ArrayList'}">

+ 187 - 0
src/main/resources/templates/select-deep-link.html

@@ -0,0 +1,187 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+    <title>Learn Center | Programs</title>
+    <meta charset="utf-8">
+    <link rel="stylesheet" href="/css/reset.css" type="text/css" media="all">
+    <link rel="stylesheet" href="/css/layout.css" type="text/css" media="all">
+    <link rel="stylesheet" href="/css/style.css" type="text/css" media="all">
+    <script type="text/javascript" src="/js/jquery-1.5.2.js"></script>
+    <script type="text/javascript" src="/js/cufon-yui.js"></script>
+    <script type="text/javascript" src="/js/cufon-replace.js"></script>
+    <script type="text/javascript" src="/js/Molengo_400.font.js"></script>
+    <script type="text/javascript" src="/js/Expletus_Sans_400.font.js"></script>
+    <!--[if lt IE 9]>
+    <script type="text/javascript" src="/js/html5.js"></script>
+    <style type="text/css">.bg, .box2 {
+        behavior: url("/js/PIE.htc");
+    }</style>
+    <![endif]-->
+</head>
+<body id="page3">
+<div class="body1">
+    <div class="main">
+        <!-- header -->
+        <header>
+            <div class="wrapper">
+                <nav>
+                    <ul id="menu">
+                        <li><a href="index.html">About</a></li>
+                        <li><a href="courses.html">Courses</a></li>
+                        <li><a href="programs.html">Programs</a></li>
+                        <li><a href="teachers.html">Teachers</a></li>
+                        <li><a href="admissions.html">Admissions</a></li>
+                        <li class="end"><a href="contacts.html">Contacts</a></li>
+                    </ul>
+                </nav>
+                <ul id="icons">
+                    <li><a href="#"><img src="/img/icons1.jpg" alt=""></a></li>
+                    <li><a href="#"><img src="/img/icons2.jpg" alt=""></a></li>
+                </ul>
+            </div>
+            <div class="wrapper">
+                <h1><a href="index.html" id="logo">Learn Center</a></h1>
+            </div>
+            <div id="slogan"> We Will Open The World<span>of knowledge for you!</span></div>
+        </header>
+        <!-- / header -->
+    </div>
+</div>
+<div class="body2">
+    <div class="main">
+        <!-- content -->
+        <section id="content">
+            <div class="box1">
+                <div class="wrapper">
+                    <article class="col1">
+                        <div class="pad_left1">
+                            <h2 class="pad_bot1">Academic Programs</h2>
+                        </div>
+                        <div class="wrapper pad_bot2">
+                            <figure class="left marg_right1"><img src="/img/page3_img1.jpg" alt=""></figure>
+                            <p class="pad_bot1 pad_top2"><strong>Whats in a LTI Message?</strong> <br>
+                                This program displays all the claims and metadata in the originating launch.</p>
+                            <form method="post" action="/callback/storedeeplink">
+                                 <input type="hidden" name="id_token" th:value="${queryParameters.id_token}">
+                                <input type="hidden" name="state" th:value="${queryParameters.state}">
+                                 <input type="hidden" name="url" value="https://www.imsglobal.org/">
+                                 <input type="hidden" name="type" value="url">
+                                 <input type="hidden" name="title" value="Whats in a LTI Message?">
+                                 <input type="submit" value="Link to this program..." class="button marg_left1">
+                            </form>
+                        </div>
+                        <div class="wrapper pad_bot2">
+                            <figure class="left marg_right1"><img src="/img/page3_img2.jpg" alt=""></figure>
+                            <p class="pad_bot1 pad_top2"><strong>Who is Riomhaire Research?</strong> <br>
+                                This program redirects the student to the Riomhaire Research Website.</p>
+                            <form method="post" action="/callback/storedeeplink">
+                                <input type="hidden" name="id_token" th:value="${queryParameters.id_token}">
+                                <input type="hidden" name="state" th:value="${queryParameters.state}">
+                                <input type="hidden" name="url" value="https://www.riomhaire.com">
+                                <input type="hidden" name="type" value="url">
+                                <input type="hidden" name="title" value="Who is Riomhaire Research?">
+                                <input type="submit" value="Link to this program..." class="button marg_left1">
+                            </form>                        </div>
+                        <div class="wrapper">
+                            <figure class="left marg_right1"><img src="/img/page3_img3.jpg" alt=""></figure>
+                            <p class="pad_bot1 pad_top2"><strong>Trivia quiz</strong> <br>
+                                This program displays a free online quiz site. This program will eventually
+                              be replaces with a grade pass-back offering.
+                            </p>
+                            <form method="post" action="/callback/storedeeplink">
+                                <input type="hidden" name="id_token" th:value="${queryParameters.id_token}">
+                                <input type="hidden" name="state" th:value="${queryParameters.state}">
+                                <input type="hidden" name="url" value="https://lovattspuzzles.com/online-puzzles-competitions/ultimate-online-trivia-quiz/">
+                                <input type="hidden" name="type" value="url">
+                                <input type="hidden" name="title" value="Trivia quiz">
+                                <input type="submit" value="Link to this program..." class="button marg_left1">
+                            </form>
+                        </div>
+                    </article>
+<!--                    <article class="col2 pad_left2">-->
+<!--                        <div class="pad_left1">-->
+<!--                            <h2>New Programs</h2>-->
+<!--                        </div>-->
+<!--                        <ul class="list1">-->
+<!--                            <li><a href="#">International Studies</a></li>-->
+<!--                            <li><a href="#">Models &amp; Language Reaching</a></li>-->
+<!--                            <li><a href="#">Public School Facts</a></li>-->
+<!--                            <li><a href="#">State Testing Data</a></li>-->
+<!--                            <li><a href="#">Education Jobs</a></li>-->
+<!--                        </ul>-->
+<!--                        <div class="pad_left1">-->
+<!--                            <h2>What is This?</h2>-->
+<!--                            <p class="quot"></p>-->
+<!--                        </div>-->
+<!--                        <a href="#"-->
+<!--                           class="button marg_top1"><span><span>&nbsp;&nbsp; View All &nbsp;&nbsp;</span></span></a>-->
+<!--                    </article>-->
+                </div>
+            </div>
+        </section>
+        <!-- content -->
+        <!-- footer -->
+        <footer>
+            <div class="wrapper">
+                <div class="pad1">
+                    <div class="pad_left1">
+                        <div class="wrapper">
+                            <article class="col_1">
+                                <h3>Address:</h3>
+                                <p class="col_address"><strong> Country:<br>
+                                    City:<br>
+                                    Address:<br>
+                                    Email: </strong></p>
+                                <p>USA<br>
+                                    San Diego<br>
+                                    Beach st. 54<br>
+                                    <a href="#">lcenter@mail.com</a></p>
+                            </article>
+                            <article class="col_2 pad_left2">
+                                <h3>Join In:</h3>
+                                <ul class="list2">
+                                    <li><a href="#">Sign Up</a></li>
+                                    <li><a href="#">Forums</a></li>
+                                    <li><a href="#">Promotions</a></li>
+                                    <li><a href="#">Lorem</a></li>
+                                </ul>
+                            </article>
+                            <article class="col_3 pad_left2">
+                                <h3>Why Us:</h3>
+                                <ul class="list2">
+                                    <li><a href="#">Lorem ipsum dolor </a></li>
+                                    <li><a href="#">Aonsect adipisic</a></li>
+                                    <li><a href="#">Eiusmjkod tempor </a></li>
+                                    <li><a href="#">Incididunt ut labore </a></li>
+                                </ul>
+                            </article>
+                            <article class="col_4 pad_left2">
+                                <h3>Newsletter:</h3>
+                                <form id="newsletter" action="#" method="post">
+                                    <div class="wrapper">
+                                        <div class="bg">
+                                            <input type="text">
+                                        </div>
+                                    </div>
+                                    <a href="#" class="button"><span><span><strong>Subscribe</strong></span></span></a>
+                                </form>
+                            </article>
+                        </div>
+                        <div class="wrapper">
+                            <article class="call"><span class="call1">Call Us Now: </span><span class="call2">1-800-567-8934</span>
+                            </article>
+                            <article class="col_4 pad_left2">Copyright &copy; <a href="#">Domain Name</a> All Rights
+                                Reserved<br>
+                                Design by <a target="_blank"
+                                             href="http://www.templatemonster.com/">TemplateMonster.com</a></article>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </footer>
+        <!-- / footer -->
+    </div>
+</div>
+<script type="text/javascript">Cufon.now();</script>
+</body>
+</html>

+ 20 - 0
src/main/resources/templates/store-deep-link-in-lms.html

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+    <title>Learn Center | Callback</title>
+    <meta charset="utf-8">
+    <!--[if lt IE 9]>
+    <script type="text/javascript" src="/js/html5.js"></script>
+    <![endif]-->
+</head>
+<body id="page3" onload="document.lmsCallbackForm.submit();">
+<div class="body1">
+      Are you sure?
+     <form name="lmsCallbackForm" method="post" th:action="${targetURL}">
+          <input type="hidden" id="JWT" name="JWT" th:value="${JWT}">
+         <input type="hidden" id="state" name="state" th:value="${state}">
+         <input type="submit" value="Yes">
+     </form>
+</div>
+</body>
+</html>

File diff suppressed because it is too large
+ 11 - 0
src/test/java/riomhaire/lti/core/business/commands/PrivateKeyTest.java


Some files were not shown because too many files changed in this diff