Permalink
Browse files

initial commit of the lti outcomes service example tool

  • Loading branch information...
codekitchen committed Dec 1, 2011
0 parents commit d903e9c87f91c44a3bcfe1f5aa8230ef4bb45494
Showing with 229 additions and 0 deletions.
  1. +4 −0 Gemfile
  2. +19 −0 Gemfile.lock
  3. +70 −0 README.md
  4. BIN assignment_config.png
  5. +136 −0 lti_example.rb
  6. BIN tool_config.png
@@ -0,0 +1,4 @@
+source "http://rubygems.org"
+
+gem "sinatra", "1.3.1"
+gem "oauth", "0.4.5"
@@ -0,0 +1,19 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ oauth (0.4.5)
+ rack (1.3.5)
+ rack-protection (1.1.4)
+ rack
+ sinatra (1.3.1)
+ rack (~> 1.3, >= 1.3.4)
+ rack-protection (~> 1.1, >= 1.1.2)
+ tilt (~> 1.3, >= 1.3.3)
+ tilt (1.3.3)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ oauth (= 0.4.5)
+ sinatra (= 1.3.1)
@@ -0,0 +1,70 @@
+LTI Outcome Service Example
+---------------------------
+
+## Instructure Canvas
+
+This is a simple example of writing an LTI tool provider that utilizes
+the outcome service support in Canvas to post a grade back to Canvas
+when the user finishes using the tool. Rather than presenting a real
+assessment, this tool will just present the user with an HTML form text
+field where they can enter their desired score.
+
+The tool is written in Ruby using the Sinatra web framework and the
+OAuth gem. You'll need to have ruby installed, with rubygems.
+
+### Running the Tool
+
+To run this tool, download the git repository, then open a console
+prompt in the repo directory.
+
+First, if you don't already have Bundler installed, do that.
+
+ $ gem install bundler
+
+You may need to run this command as root depending on your ruby
+configuration.
+
+Next you'll want to install the necessary gems:
+
+ $ bundle install
+
+Now, just start it up:
+
+ $ ruby -rubygems lti_example.rb
+
+You will see some output like this:
+
+ Sinatra/1.3.1 has taken the stage on 4567 for development with backup from WEBrick
+
+That number is the port that the tool is running on (4567 is the default).
+
+### Configuring Canvas
+
+Next, you'll need to configure a course in Canvas to point to your
+now-running LTI tool. The first step is to add the tool to the course
+settings, external tools tab. The consumer key is "test", the secret is
+"secret". The URL will likely be
+"http://localhost:4567/assessment/start", though the hostname and port
+may change if you are not running the tool on the same computer as your
+web browser.
+
+Here is what your configuration should look like:
+
+![tool config](/codekitchen/lti_outcomes_example/raw/master/tool_config.png)
+
+Now that the tool is configured, you'll need to set up an Assignment
+that points to the tool. This step is what lets you define how many
+points the tool is worth, and links the tool into the course gradebook
+in Canvas.
+
+On the Assignments tab in the same course, create a new assignment of
+type "External Tool". Click the "more options" link, and you'll see a
+dialog asking you which tool to use:
+
+![assignment config](/codekitchen/lti_outcomes_example/raw/master/assignment_config.png)
+
+Select your newly-configured tool and save the assignment. Now when you
+go to the assignment page as a student, the tool will launch, and you
+can take the external tool assessment! Once you've stepped through the
+assessment, you can look at the grades tab in Canvas and verify that the
+grade from the tool was saved into Canvas.
Binary file not shown.
@@ -0,0 +1,136 @@
+begin
+ require 'rubygems'
+rescue LoadError
+ puts "You must install rubygems to run this example"
+ raise
+end
+
+begin
+ require 'bundler/setup'
+rescue LoadError
+ puts "to set up this example, run these commands:"
+ puts " gem install bundler"
+ puts " bundle install"
+ raise
+end
+
+require 'sinatra'
+require 'oauth'
+require 'oauth/request_proxy/rack_request'
+
+# hard-coded oauth information for testing convenience
+$oauth_key = "test"
+$oauth_secret = "secret"
+
+# sinatra wants to set x-frame-options by default, disable it
+disable :protection
+# enable sessions so we can remember the launch info between http requests, as
+# the user takes the assessment
+enable :sessions
+
+# this is the entry action that Canvas (the LTI Tool Consumer) sends the
+# browser to when launching the tool.
+post "/assessment/start" do
+ # first we have to verify the oauth signature, to make sure this isn't an
+ # attempt to hack the planet
+ begin
+ signature = OAuth::Signature.build(request, :consumer_secret => $oauth_secret)
+ signature.verify() or raise OAuth::Unauthorized
+ rescue OAuth::Signature::UnknownSignatureMethod,
+ OAuth::Unauthorized
+ return %{unauthorized attempt. make sure you used the consumer secret "#{$oauth_secret}"}
+ end
+
+ # make sure this is an assignment tool launch, not another type of launch.
+ # only assignment tools support the outcome service, since only they appear
+ # in the Canvas gradebook.
+ unless params['lis_outcome_service_url'] && params['lis_result_sourcedid']
+ return %{It looks like this LTI tool wasn't launched as an assignment, or you are trying to take it as a teacher rather than as a a student. Make sure to set up an external tool assignment as outlined in the README for this example.}
+ end
+
+ # store the relevant parameters from the launch into the user's session, for
+ # access during subsequent http requests.
+ # note that the name and email might be blank, if the tool wasn't configured
+ # in Canvas to provide that private information.
+ %w(lis_outcome_service_url lis_result_sourcedid lis_person_name_full lis_person_contact_email_primary).each { |v| session[v] = params[v] }
+
+ # that's it, setup is done. now send them to the assessment!
+ redirect to("/assessment")
+end
+
+def username
+ session['lis_person_name_full'] || 'Student'
+end
+
+get "/assessment" do
+ # first make sure they got here through a tool launch
+ unless session['lis_result_sourcedid']
+ return %{You need to take this assessment through Canvas.}
+ end
+
+ # now render a simple form the user will submit to "take the quiz"
+ <<-HTML
+ <html>
+ <head><title>Demo LTI Assessment Tool</title></head>
+ <body>
+ <h1>Demo LTI Assessment Tool</h1>
+ <form action="/assessment" method="post">
+ <p>Hi, #{username}. On a scale of <code>0.0</code> to <code>1.0</code>, how well would you say you did on this assessment?</p>
+ <input name='score' type='text' width='5' id='score' />
+ <input type='submit' value='Submit' />
+ <p>If you want to enter an invalid score here, you can see how Canvas will reject it.</p>
+ </form>
+ </body>
+ </html>
+ HTML
+end
+
+# This is the action that the form submits to with the score that the student entered.
+# In lieu of a real assessment, that score is then just submitted back to Canvas.
+post "/assessment" do
+ # obviously in a real tool, we're not going to let the user input their own score
+ score = params['score']
+ if !score || score.empty?
+ redirect to("/assessment")
+ end
+
+ # now post the score to canvas. Make sure to sign the POST correctly with
+ # OAuth 1.0, including the digest of the XML body. Also make sure to set the
+ # content-type to application/xml.
+ xml = %{
+<?xml version = "1.0" encoding = "UTF-8"?>
+<imsx_POXEnvelopeRequest xmlns = "http://www.imsglobal.org/lis/oms1p0/pox">
+ <imsx_POXHeader>
+ <imsx_POXRequestHeaderInfo>
+ <imsx_version>V1.0</imsx_version>
+ <imsx_messageIdentifier>12341234</imsx_messageIdentifier>
+ </imsx_POXRequestHeaderInfo>
+ </imsx_POXHeader>
+ <imsx_POXBody>
+ <replaceResultRequest>
+ <resultRecord>
+ <sourcedGUID>
+ <sourcedId>#{session['lis_result_sourcedid']}</sourcedId>
+ </sourcedGUID>
+ <result>
+ <resultScore>
+ <language>en</language>
+ <textString>#{score}</textString>
+ </resultScore>
+ </result>
+ </resultRecord>
+ </replaceResultRequest>
+ </imsx_POXBody>
+</imsx_POXEnvelopeRequest>
+ }
+ consumer = OAuth::Consumer.new($oauth_key, $oauth_secret)
+ token = OAuth::AccessToken.new(consumer)
+ response = token.post(session['lis_outcome_service_url'], xml, 'Content-Type' => 'application/xml')
+
+ headers 'Content-Type' => 'text'
+ %{
+Your score has #{response.body.match(/\bsuccess\b/) ? "been posted" : "failed in posting"} to Canvas. The response was:
+
+#{response.body}
+ }
+end
Binary file not shown.

0 comments on commit d903e9c

Please sign in to comment.