user1927468
user1927468

Reputation: 1133

QTIWorks LTI request parameter is empty

I'm trying to implement LTI in our education platform to connect with QTIWoks in Java.

I have a simple tool consumer that generates the following HTML Form:

<html>
<head> </head>
<body>
<form action="http://192.168.0.114:8080/qtiworks-engine/lti/domainlaunch" name="ltiLaunchForm" id="ltiLaunchForm" method="post" target="basicltiLaunchFrame" enctype="application/x-www-form-urlencoded" style="display: block;">
<input type="hidden" name="context_id" value="cid-00113">
<input type="hidden" name="context_label" value="SI106">
<input type="hidden" name="context_title" value="Design of Personal Environments 1">
<input type="hidden" name="ext_note" value="Instructor from first course">
<input type="hidden" name="launch_presentation_locale" value="en_us">
<input type="hidden" name="lis_person_contact_email_primary" value="[email protected]">
<input type="hidden" name="lis_person_name_family" value="Instructor">
<input type="hidden" name="lis_person_name_given" value="Siân">
<input type="hidden" name="lis_person_sourcedid" value="school.edu:user">
<input type="hidden" name="resource_link_description" value="This learning space is private">
<input type="hidden" name="resource_link_id" value="res-0012612">
<input type="hidden" name="resource_link_title" value="My Weekly Wiki">
<input type="hidden" name="roles" value="Instructor">
<input type="hidden" name="tool_consumer_info_product_family_code" value="sakai-unit">
<input type="hidden" name="tool_consumer_info_version" value="0.9">
<input type="hidden" name="tool_consumer_instance_description" value="University of School (LMSng)">
<input type="hidden" name="tool_consumer_instance_guid" value="lmsng.school.edu">
<input type="hidden" name="user_id" value="user-0016">
<input type="hidden" name="oauth_callback" value="about:blank">
<input type="hidden" name="custom_simple_key" value="custom_simple_value">
<input type="hidden" name="custom_complex____________key" value="Complex!@#$^*(){}[]½Value">
<input type="hidden" name="lti_version" value="LTI-1p0">
<input type="hidden" name="lti_message_type" value="basic-lti-launch-request">
<input type="hidden" name="oauth_version" value="1.0">
<input type="hidden" name="oauth_nonce" value="Z2WVNEPUZkzsolOe4hRvKbkXtOSmYiyw">
<input type="hidden" name="oauth_timestamp" value="1429793062">
<input type="hidden" name="oauth_consumer_key" value="feras">
<input type="hidden" name="oauth_signature_method" value="HMAC-SHA1">
<input type="hidden" name="oauth_signature" value="g908qTtVGh8MsOgVUdarVlSBmC0=">
<input type="hidden" name="ext_submit" value="Finish Launch">
</form><iframe name="basicltiLaunchFrame" id="basicltiLaunchFrame" src="" width="100%" height="900" scrolling="auto" frameborder="1" transparency="">
</iframe>
<script type="text/javascript">
   document.getElementById("ltiLaunchForm").style.display = "none";
   nei = document.createElement('input');
   nei.setAttribute('type', 'hidden');
   nei.setAttribute('name', 'ext_submit');
   nei.setAttribute('value', 'FinishLaunch');
   document.getElementById("ltiLaunchForm").appendChild(nei);
   document.ltiLaunchForm.submit();
</script>
</body>
</html>

then it calls the dominlaunch from qtiworks

@RequestMapping(value="/domainlaunch", method=RequestMethod.POST)
    public String ltiDomainLevelLaunch(final HttpSession httpSession, final HttpServletRequest request,
            final HttpServletResponse response)
            throws IOException {
        /* Decode LTI launch request, and bail out on error */
        final DecodedLtiLaunch decodedLtiLaunch = ltiLaunchService.decodeLtiLaunchData(request, LtiLaunchType.DOMAIN);
        if (decodedLtiLaunch.isError()) {
            response.sendError(decodedLtiLaunch.getErrorCode(), decodedLtiLaunch.getErrorMessage());
            return null;
        }
        final LtiLaunchData ltiLaunchData = decodedLtiLaunch.getLtiLaunchData();

        /* Make sure this is a domain launch */
        final LtiUser ltiUser = decodedLtiLaunch.getLtiUser();
        final LtiDomain ltiDomain = ltiUser.getLtiDomain();
        if (ltiDomain==null) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "The tool consumer has attempted a domain-level launch using a link-level key");
            return null;
        }

        /* Extract/create the corresponding LtiResource for this launch */
        final LtiResource ltiResource = ltiLaunchService.provideLtiResource(decodedLtiLaunch); /* (May be null for candidates) */
        final UserRole userRole = ltiUser.getUserRole();

        if (userRole==UserRole.INSTRUCTOR) {
            /* If user is an instructor, we'll forward to the LTI instructor MVC after
             * "authenticating" the user by creating and storing an LtiDomainTicket
             * in the session */
            final LtiAuthenticationTicket ltiDomainTicket = new LtiAuthenticationTicket(ltiUser.getId(), ltiResource.getId(),ltiLaunchData.getLaunchPresentationReturnUrl());
            LtiResourceAuthenticationFilter.authenticateUserForResource(httpSession, ltiDomainTicket);
            return "redirect:/lti/resource/" + ltiResource.getId();
        }
        else if (userRole==UserRole.CANDIDATE) {
            /* If user is a candidate, then we'll launch/reuse a candidate session */
            if (ltiResource==null) {
                return "candidateLaunchError";
            }

            /* Extract relevant data */
            final String returnUrl = ltiLaunchData.getLaunchPresentationReturnUrl();
            final String lisOutcomeServiceUrl = ltiLaunchData.getLisOutcomeServiceUrl();
            final String lisResultSourcedid = ltiLaunchData.getLisResultSourcedid();

            /* Launch and redirect to session */
            try {
                final CandidateSessionTicket candidateSessionTicket = candidateSessionLaunchService.launchDomainLevelLtiCandidateSession(httpSession,
                        ltiUser, ltiResource, returnUrl, lisOutcomeServiceUrl, lisResultSourcedid);
                return GlobalRouter.buildSessionStartRedirect(candidateSessionTicket);
            }
            catch (final CandidateException e) {
                return "candidateLaunchError";
            }
        }
        else {
            throw new QtiWorksLogicException("Unexpected LTI userRole " + userRole);
        }
    }

the problem is decodedLtiLaunch.isError() returns true and my request is not performed.

I debugged the problem and found that HttpServletRequest request has no item in parameterMap

however, it's working well when the request comes from moodle (request's parameterMap has the passed params)

how can I solve this please?

thanks in advance.

Upvotes: 1

Views: 321

Answers (1)

pfranza
pfranza

Reputation: 3367

Without knowing what the exact error code you are seeing is, I am going to make a very strong guess that your signature is bad

Since you are using JAVA I would suggest that you utilize the basiclti-util library that IMSGlobal provides to generate a signature

Add the following dependency

<dependency>
  <groupId>org.imsglobal</groupId>
  <artifactId>basiclti-util</artifactId>
  <version>1.1.2</version>
</dependency>

Then use the following code to generate the signature

Map<String, String> signedParameters = new LtiOauthSigner()
   .signParameters(parameters, key, secret, url, "POST");

Then take all of the key-value pairs in the signedParameters map, and use that to construct the form input tags you have in your example.

Upvotes: 0

Related Questions