forked from whitmer/edu_apps
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlti_example.rb
385 lines (353 loc) · 16.6 KB
/
lti_example.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
require 'sinatra'
require 'ims/lti'
require 'json'
require 'dm-core'
require 'dm-migrations'
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
class ExternalConfig
include DataMapper::Resource
property :id, Serial
property :config_type, String
property :value, String
end
configure do
DataMapper.setup(:default, (ENV["DATABASE_URL"] || "sqlite3:///#{Dir.pwd}/development.sqlite3"))
DataMapper.auto_upgrade!
@@quizlet_config = ExternalConfig.first(:config_type => 'quizlet')
end
# this is the entry action that Canvas (the LTI Tool Consumer) sends the
# browser to when launching the tool.
post "/assessment/start" do
# create a ToolProvider object to verify the request
@tool_provider = IMS::LTI::ToolProvider.new($oauth_key, $oauth_secret, params)
# first we have to verify the oauth signature, to make sure this isn't an
# attempt to hack the planet
if !@tool_provider.valid_request?(request)
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 @tool_provider.outcome_service?
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 <a target="_blank" href="https://github.com/instructure/lti_example">in the README</a> 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.
session['launch_params'] = @tool_provider.to_params
# that's it, setup is done. now send them to the assessment!
redirect to("/assessment")
end
get "/assessment" do
@tool_provider = IMS::LTI::ToolProvider.new($oauth_key, $oauth_secret, session['launch_params'])
# first make sure they got here through a tool launch
unless @tool_provider.outcome_service?
return %{You need to take this assessment through Canvas.}
end
@username = @tool_provider.username || "Dude"
# now render a simple form the user will submit to "take the quiz"
erb :assessment
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
@tool_provider = IMS::LTI::ToolProvider.new($oauth_key, $oauth_secret, session['launch_params'])
# 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
response = @tool_provider.post_outcome(score)
headers 'Content-Type' => 'text'
<<-TEXT
Your score has #{@tool_provider.outcome_post_successful? ? "been posted" : "failed in posting"} to Canvas. The response was:
Response code: #{response.code}
#{response.body}
TEXT
end
get "/" do
erb :index
end
get "/quizlet_search" do
return "Quizlet not propertly configured" unless @@quizlet_config
uri = URI.parse("https://api.quizlet.com/2.0/search/sets")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
tmp_url = uri.path+"?q=#{params['q']}&client_id=#{@@quizlet_config.value}"
request = Net::HTTP::Get.new(tmp_url)
response = http.request(request)
return response.body
end
def config_wrap(xml)
res = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<cartridge_basiclti_link xmlns="http://www.imsglobal.org/xsd/imslticc_v1p0"
xmlns:blti = "http://www.imsglobal.org/xsd/imsbasiclti_v1p0"
xmlns:lticm ="http://www.imsglobal.org/xsd/imslticm_v1p0"
xmlns:lticp ="http://www.imsglobal.org/xsd/imslticp_v1p0"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.imsglobal.org/xsd/imslticc_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticc_v1p0.xsd
http://www.imsglobal.org/xsd/imsbasiclti_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imsbasiclti_v1p0.xsd
http://www.imsglobal.org/xsd/imslticm_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticm_v1p0.xsd
http://www.imsglobal.org/xsd/imslticp_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticp_v1p0.xsd">
XML
res += xml
res += <<-XML
<cartridge_bundle identifierref="BLTI001_Bundle"/>
<cartridge_icon identifierref="BLTI001_Icon"/>
</cartridge_basiclti_link>
XML
end
get "/config/course_navigation.xml" do
host = request.scheme + "://" + request.host_with_port
headers 'Content-Type' => 'text/xml'
config_wrap <<-XML
<blti:title>Course Wanda Fish</blti:title>
<blti:description>This tool adds a course navigation link to a page on a fish called "Wanda"</blti:description>
<blti:launch_url>#{host}/tool_redirect</blti:launch_url>
<blti:extensions platform="canvas.instructure.com">
<lticm:property name="privacy_level">public</lticm:property>
<lticm:options name="course_navigation">
<lticm:property name="url">#{host}/tool_redirect?url=#{CGI.escape('/images.html?custom_fish_name=wanda')}</lticm:property>
<lticm:property name="text">Course Wanda Fish</lticm:property>
</lticm:options>
</blti:extensions>
XML
end
get "/config/account_navigation.xml" do
host = request.scheme + "://" + request.host_with_port
headers 'Content-Type' => 'text/xml'
config_wrap <<-XML
<blti:title>Account Phil Fish</blti:title>
<blti:description>This tool adds an account navigation link to a page on a fish named "Phil"</blti:description>
<blti:launch_url>#{host}/tool_redirect</blti:launch_url>
<blti:extensions platform="canvas.instructure.com">
<lticm:property name="privacy_level">public</lticm:property>
<lticm:options name="account_navigation">
<lticm:property name="url">#{host}/tool_redirect?url=#{CGI.escape('/images.html?custom_fish_name=phil')}</lticm:property>
<lticm:property name="text">Account Phil Fish</lticm:property>
</lticm:options>
</blti:extensions>
XML
end
get "/config/user_navigation.xml" do
host = request.scheme + "://" + request.host_with_port
headers 'Content-Type' => 'text/xml'
config_wrap <<-XML
<blti:title>User Alexander Fish</blti:title>
<blti:description>This tool adds a user navigation link (in a user's profile) to a page on a fish called "Alexander"</blti:description>
<blti:launch_url>#{host}/tool_redirect</blti:launch_url>
<blti:extensions platform="canvas.instructure.com">
<lticm:property name="privacy_level">public</lticm:property>
<lticm:options name="user_navigation">
<lticm:property name="url">#{host}/tool_redirect?url=#{CGI.escape('/images.html?custom_fish_name=alexander')}</lticm:property>
<lticm:property name="text">User Alexander Fish</lticm:property>
</lticm:options>
</blti:extensions>
<blti:icon>#{host}/fish_icon.png</blti:icon>
XML
end
get "/config/grade_passback.xml" do
host = request.scheme + "://" + request.host_with_port
headers 'Content-Type' => 'text/xml'
config_wrap <<-XML
<blti:title>Grade Passback Demo</blti:title>
<blti:description>This tool demos the LTI Outcomes (grade passback) available as part of LTI</blti:description>
<blti:launch_url>#{host}/assessment/start</blti:launch_url>
<blti:extensions platform="canvas.instructure.com">
<lticm:property name="privacy_level">public</lticm:property>
</blti:extensions>
XML
end
get "/config/editor_button.xml" do
host = request.scheme + "://" + request.host_with_port
headers 'Content-Type' => 'text/xml'
config_wrap <<-XML
<blti:title>I Like Fish</blti:title>
<blti:description>I'm a big fan of fish, and I want to share the love</blti:description>
<blti:launch_url>#{host}/tool_redirect</blti:launch_url>
<blti:extensions platform="canvas.instructure.com">
<lticm:property name="privacy_level">public</lticm:property>
<lticm:options name="editor_button">
<lticm:property name="url">#{host}/tool_redirect?url=#{CGI.escape('/images.html')}</lticm:property>
<lticm:property name="icon_url">#{host}/fish_icon.png</lticm:property>
<lticm:property name="text">Pick a Fish</lticm:property>
<lticm:property name="selection_width">500</lticm:property>
<lticm:property name="selection_height">300</lticm:property>
</lticm:options>
</blti:extensions>
<blti:icon>#{host}/fish_icon.png</blti:icon>
XML
end
get "/config/editor_button2.xml" do
host = request.scheme + "://" + request.host_with_port
headers 'Content-Type' => 'text/xml'
config_wrap <<-XML
<blti:title>Placekitten.com</blti:title>
<blti:description>Placekitten.com is a quick and simple service for adding pictures of kittens to your site</blti:description>
<blti:launch_url>#{host}/tool_redirect</blti:launch_url>
<blti:extensions platform="canvas.instructure.com">
<lticm:property name="privacy_level">public</lticm:property>
<lticm:options name="editor_button">
<lticm:property name="url">#{host}/tool_redirect?url=#{CGI.escape('/kitten.html')}</lticm:property>
<lticm:property name="icon_url">#{host}/cat_icon.png</lticm:property>
<lticm:property name="text">Insert a Kitten</lticm:property>
<lticm:property name="selection_width">500</lticm:property>
<lticm:property name="selection_height">400</lticm:property>
</lticm:options>
</blti:extensions>
<blti:icon>#{host}/cat_icon.png</blti:icon>
XML
end
get "/config/resource_selection.xml" do
host = request.scheme + "://" + request.host_with_port
headers 'Content-Type' => 'text/xml'
config_wrap <<-XML
<blti:title>I Like Fish</blti:title>
<blti:description>I'm a big fan of fish, and I want to share the love</blti:description>
<blti:launch_url>#{host}/tool_redirect</blti:launch_url>
<blti:extensions platform="canvas.instructure.com">
<lticm:property name="privacy_level">public</lticm:property>
<lticm:options name="resource_selection">
<lticm:property name="url">#{host}/tool_redirect?url=#{CGI.escape('/name.html')}</lticm:property>
<lticm:property name="text">Pick a Fish Name</lticm:property>
<lticm:property name="selection_width">500</lticm:property>
<lticm:property name="selection_height">300</lticm:property>
</lticm:options>
</blti:extensions>
XML
end
get "/config/editor_button_and_resource_selection.xml" do
host = request.scheme + "://" + request.host_with_port
headers 'Content-Type' => 'text/xml'
config_wrap <<-XML
<blti:title>I Like Fish</blti:title>
<blti:description>I'm a big fan of fish, and I want to share the love</blti:description>
<blti:launch_url>#{host}/tool_redirect</blti:launch_url>
<blti:extensions platform="canvas.instructure.com">
<lticm:property name="privacy_level">public</lticm:property>
<lticm:options name="editor_button">
<lticm:property name="url">#{host}/tool_redirect?url=#{CGI.escape('/images.html')}</lticm:property>
<lticm:property name="icon_url">#{host}/fish_icon.png</lticm:property>
<lticm:property name="text">Pick a Fish</lticm:property>
<lticm:property name="selection_width">500</lticm:property>
<lticm:property name="selection_height">300</lticm:property>
</lticm:options>
<lticm:options name="resource_selection">
<lticm:property name="url">#{host}/tool_redirect?url=#{CGI.escape('/name.html')}</lticm:property>
<lticm:property name="text">Pick a Fish Name</lticm:property>
<lticm:property name="selection_width">500</lticm:property>
<lticm:property name="selection_height">300</lticm:property>
</lticm:options>
</blti:extensions>
XML
end
get "/config/inline_graph.xml" do
host = request.scheme + "://" + request.host_with_port
headers 'Content-Type' => 'text/xml'
config_wrap <<-XML
<blti:title>Embeddable Graphs</blti:title>
<blti:description>This tool allows for the creation and insertion of rich, interactive graphs.</blti:description>
<blti:launch_url>#{host}/tool_redirect</blti:launch_url>
<blti:extensions platform="canvas.instructure.com">
<lticm:property name="privacy_level">public</lticm:property>
<lticm:options name="editor_button">
<lticm:property name="url">#{host}/tool_redirect?url=#{CGI.escape('/graph.html')}</lticm:property>
<lticm:property name="icon_url">#{host}/graph.tk/favicon.ico</lticm:property>
<lticm:property name="text">Embed Graph</lticm:property>
<lticm:property name="selection_width">740</lticm:property>
<lticm:property name="selection_height">450</lticm:property>
</lticm:options>
</blti:extensions>
<blti:icon>#{host}/graph.tk/favicon.ico</blti:icon>
XML
end
get "/config/khan_academy.xml" do
host = request.scheme + "://" + request.host_with_port
headers 'Content-Type' => 'text/xml'
config_wrap <<-XML
<blti:title>Khan Academy Videos</blti:title>
<blti:description>Search for and insert links to Khan Academy lecture videos.</blti:description>
<blti:launch_url>#{host}/tool_redirect</blti:launch_url>
<blti:extensions platform="canvas.instructure.com">
<lticm:property name="privacy_level">public</lticm:property>
<lticm:options name="editor_button">
<lticm:property name="url">#{host}/tool_redirect?url=#{CGI.escape('/khan.html')}</lticm:property>
<lticm:property name="icon_url">#{host}/khan.ico</lticm:property>
<lticm:property name="text">Find Khan Academy Video</lticm:property>
<lticm:property name="selection_width">590</lticm:property>
<lticm:property name="selection_height">450</lticm:property>
</lticm:options>
</blti:extensions>
<blti:icon>#{host}/khan.ico</blti:icon>
XML
end
get "/config/quizlet.xml" do
host = request.scheme + "://" + request.host_with_port
headers 'Content-Type' => 'text/xml'
config_wrap <<-XML
<blti:title>Quizlet Flash Cards</blti:title>
<blti:description>Search for and insert publicly available flash card sets from quizlet.com</blti:description>
<blti:launch_url>#{host}/tool_redirect</blti:launch_url>
<blti:extensions platform="canvas.instructure.com">
<lticm:property name="privacy_level">public</lticm:property>
<lticm:options name="editor_button">
<lticm:property name="url">#{host}/tool_redirect?url=#{CGI.escape('/quizlet.html')}</lticm:property>
<lticm:property name="icon_url">#{host}/quizlet.ico</lticm:property>
<lticm:property name="text">Embed Quizlet Flash Cards</lticm:property>
<lticm:property name="selection_width">690</lticm:property>
<lticm:property name="selection_height">510</lticm:property>
</lticm:options>
</blti:extensions>
<blti:icon>#{host}/khan.ico</blti:icon>
XML
end
get "/config/tools.xml" do
host = request.scheme + "://" + request.host_with_port
headers 'Content-Type' => 'text/xml'
config_wrap <<-XML
<blti:title>Public Resource Libraries</blti:title>
<blti:description>Collection of resources from multiple sources, including Kahn Academy, Quizlet, etc.</blti:description>
<blti:launch_url>#{host}/tool_redirect</blti:launch_url>
<blti:extensions platform="canvas.instructure.com">
<lticm:property name="privacy_level">public</lticm:property>
<lticm:options name="editor_button">
<lticm:property name="url">#{host}/tool_redirect?url=#{CGI.escape('/tools.html')}</lticm:property>
<lticm:property name="icon_url">#{host}/tools.png</lticm:property>
<lticm:property name="text">Search Resource Libraries</lticm:property>
<lticm:property name="selection_width">800</lticm:property>
<lticm:property name="selection_height">600</lticm:property>
</lticm:options>
</blti:extensions>
<blti:icon>#{host}/khan.ico</blti:icon>
XML
end
post "/tool_redirect" do
url = params['url']
args = []
params.each do |key, val|
args << "#{CGI.escape(key)}=#{CGI.escape(val)}" if key.match(/^custom_/) || ['launch_presentation_return_url', 'selection_directive'].include?(key)
end
url = url + (url.match(/\?/) ? "&" : "?") + args.join('&')
redirect to(url)
end
get "/oembed" do
url = params['url']
code = CGI.unescape(url.split(/code=/)[1])
{
'version' => '1.0',
'type' => 'rich',
'html' => code,
'width' => 600,
'height' => 400
}.to_json
end