lti_provider_engine 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE +20 -0
- data/README.md +145 -0
- data/Rakefile +28 -0
- data/app/controllers/lti_provider/application_controller.rb +5 -0
- data/app/controllers/lti_provider/lti_controller.rb +61 -0
- data/app/models/lti_provider/launch.rb +105 -0
- data/app/views/layouts/lti_provider/application.html.erb +12 -0
- data/app/views/lti_provider/lti/cookie_test.html.erb +4 -0
- data/config/lti.yml.example +13 -0
- data/config/lti_xml.yml.example +22 -0
- data/config/routes.rb +6 -0
- data/db/migrate/20130319050003_create_lti_provider_launches.rb +11 -0
- data/lib/lti_provider.rb +20 -0
- data/lib/lti_provider/config.rb +1 -0
- data/lib/lti_provider/engine.rb +19 -0
- data/lib/lti_provider/lti_application.rb +56 -0
- data/lib/lti_provider/lti_config.rb +28 -0
- data/lib/lti_provider/lti_xml_config.rb +23 -0
- data/lib/lti_provider/version.rb +3 -0
- data/lib/lti_provider/xml_config.rb +1 -0
- data/lib/tasks/lti_provider_tasks.rake +4 -0
- data/spec/controllers/lti_provider/lti_controller_spec.rb +141 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/welcome_controller.rb +5 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +58 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/cucumber.yml +8 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/lti.yml +13 -0
- data/spec/dummy/config/lti_xml.yml +24 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20130319050206_create_lti_provider_launches.lti_provider.rb +12 -0
- data/spec/dummy/db/schema.rb +24 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +266 -0
- data/spec/dummy/log/test.log +3643 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/robots.txt +5 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/models/lti_provider/launch_spec.rb +80 -0
- data/spec/spec_helper.rb +55 -0
- metadata +337 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
# LtiProvider
|
2
|
+
|
3
|
+
LtiProvider is a mountable engine for handling the LTI launch and exposing LTI
|
4
|
+
parameters in your rails app.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add the gem to your `Gemfile` with the following line, and then `bundle install`
|
9
|
+
|
10
|
+
```
|
11
|
+
gem 'lti_provider_engine', :require => 'lti_provider'
|
12
|
+
```
|
13
|
+
|
14
|
+
Then, mount the engine to your app by adding this line to your `routes.rb` file
|
15
|
+
|
16
|
+
```
|
17
|
+
mount LtiProvider::Engine => "/"
|
18
|
+
```
|
19
|
+
|
20
|
+
Next, include the engine in your `ApplicationController`
|
21
|
+
|
22
|
+
```
|
23
|
+
class ApplicationController < ActionController::Base
|
24
|
+
include LtiProvider::LtiApplication
|
25
|
+
|
26
|
+
...
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
After that, create `lti.yml` and `lti_xml.yml` files in your `config/` folder that looks something
|
31
|
+
like this:
|
32
|
+
|
33
|
+
**lti.yml**
|
34
|
+
|
35
|
+
```
|
36
|
+
default: &default
|
37
|
+
key: your_key
|
38
|
+
secret: your_secret
|
39
|
+
require_canvas: true
|
40
|
+
|
41
|
+
development:
|
42
|
+
<<: *default
|
43
|
+
|
44
|
+
test:
|
45
|
+
<<: *default
|
46
|
+
|
47
|
+
production:
|
48
|
+
<<: *default
|
49
|
+
```
|
50
|
+
|
51
|
+
You'll need the values of `key` and `secret` when you configure your lti app on
|
52
|
+
the tool consumer side.
|
53
|
+
|
54
|
+
**lti_xml.yml**
|
55
|
+
|
56
|
+
```
|
57
|
+
default: &default
|
58
|
+
tool_title: 'Dummy App'
|
59
|
+
tool_description: 'A very handy dummy application for testing LtiProvider engine integration.'
|
60
|
+
tool_id: 'dummy'
|
61
|
+
privacy_level: 'public'
|
62
|
+
account_navigation:
|
63
|
+
text: 'Dummy'
|
64
|
+
visibility: 'admins'
|
65
|
+
course_navigation:
|
66
|
+
text: 'Dummy'
|
67
|
+
visibility: 'admins'
|
68
|
+
|
69
|
+
development:
|
70
|
+
<<: *default
|
71
|
+
|
72
|
+
test:
|
73
|
+
<<: *default
|
74
|
+
|
75
|
+
production:
|
76
|
+
<<: *default
|
77
|
+
```
|
78
|
+
|
79
|
+
These values are used in the `/configure.xml` endpoint.
|
80
|
+
|
81
|
+
Finally, run migrations:
|
82
|
+
|
83
|
+
```
|
84
|
+
bundle install
|
85
|
+
bundle exec rake railties:install:migrations
|
86
|
+
bundle exec rake db:migrate
|
87
|
+
```
|
88
|
+
|
89
|
+
This will create the `lti_provider_launches` table which stores parameters
|
90
|
+
temporarily through a cookie test redirect. It is transient data. It can be
|
91
|
+
accessed from your main application as LtiProvider::Launch
|
92
|
+
|
93
|
+
## Usage
|
94
|
+
|
95
|
+
The engine exposes a few urls from the mount point:
|
96
|
+
|
97
|
+
* `/cookie_test`
|
98
|
+
* `/consume_launch`
|
99
|
+
* `/launch`
|
100
|
+
* `/configure.xml`
|
101
|
+
|
102
|
+
Mostly, you don't have to worry about these, they are used to route through the
|
103
|
+
lti launch. However, `/configure.xml` can be useful in configuring the app on
|
104
|
+
the tool consumer side. Right now it is hardcoded to 'Course Navigation' and
|
105
|
+
'Account navigation' apps.
|
106
|
+
|
107
|
+
The engine sets up a global `before_filter`, requiring your app to be launched
|
108
|
+
through lti. It handles receiving the request, verifying it, and exposing
|
109
|
+
certain config variables sent with the launch parameters. Specifically, it
|
110
|
+
exposes the following methods to your controllers:
|
111
|
+
|
112
|
+
* `canvas_url`
|
113
|
+
* `user_id`
|
114
|
+
* `current_course_id`
|
115
|
+
* `tool_consumer_instance_guid`
|
116
|
+
* `current_account_id`
|
117
|
+
* `course_launch?`
|
118
|
+
* `account_launch?`
|
119
|
+
|
120
|
+
## Configuring the Tool Consumer
|
121
|
+
|
122
|
+
You will need `key` and `secret` from your `lti.yml` file, and you can find
|
123
|
+
configuration xml (or at least a starting point) at
|
124
|
+
`<engine-mount-point>/configure.xml`
|
125
|
+
|
126
|
+
## Example
|
127
|
+
|
128
|
+
You can see and interact with an example of an app using this engine by looking
|
129
|
+
at `spec/dummy`. This is a full rails app which integrates the gem and has
|
130
|
+
a simple index page that says 'Hello LTI' if the app is launched through LTI.
|
131
|
+
|
132
|
+
## About LTI
|
133
|
+
|
134
|
+
Interested in learning more about LTI? Here are some links to get you started:
|
135
|
+
|
136
|
+
* [Introduction to LTI](http://www.imsglobal.org/toolsinteroperability2.cfm)
|
137
|
+
* [1.1.1 Implementation Guide](http://www.imsglobal.org/LTI/v1p1p1/ltiIMGv1p1p1.html)
|
138
|
+
|
139
|
+
## Contributing
|
140
|
+
|
141
|
+
1. Fork it
|
142
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
143
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
144
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
145
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'LtiProvider'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
24
|
+
load 'rails/tasks/engine.rake'
|
25
|
+
|
26
|
+
Bundler::GemHelper.install_tasks
|
27
|
+
|
28
|
+
task :default => :spec
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'oauth/request_proxy/rack_request'
|
2
|
+
|
3
|
+
module LtiProvider
|
4
|
+
class LtiController < ApplicationController
|
5
|
+
skip_before_filter :require_lti_launch
|
6
|
+
|
7
|
+
def launch
|
8
|
+
provider = IMS::LTI::ToolProvider.new(params['oauth_consumer_key'], LtiProvider::Config.secret, params)
|
9
|
+
launch = Launch.initialize_from_request(provider, request)
|
10
|
+
|
11
|
+
if !launch.valid_provider?
|
12
|
+
msg = "#{launch.lti_errormsg} Please be sure you are launching this tool from the link provided in Canvas."
|
13
|
+
return show_error msg
|
14
|
+
elsif launch.save
|
15
|
+
session[:cookie_test] = true
|
16
|
+
redirect_to cookie_test_path(nonce: launch.nonce)
|
17
|
+
else
|
18
|
+
return show_error "Unable to launch #{LtiProvider::XmlConfig.tool_name}. Please check your External Tools configuration and try again."
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def cookie_test
|
23
|
+
if session[:cookie_test]
|
24
|
+
# success!!! we've got a session!
|
25
|
+
consume_launch
|
26
|
+
else
|
27
|
+
render
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def consume_launch
|
32
|
+
launch = Launch.where("created_at > ?", 5.minutes.ago).find_by_nonce(params[:nonce])
|
33
|
+
|
34
|
+
if launch
|
35
|
+
[:account_id, :course_name, :course_id, :canvas_url, :tool_consumer_instance_guid,
|
36
|
+
:user_id, :user_name, :user_roles, :user_avatar_url].each do |attribute|
|
37
|
+
session[attribute] = launch.public_send(attribute)
|
38
|
+
end
|
39
|
+
|
40
|
+
launch.destroy
|
41
|
+
|
42
|
+
redirect_to main_app.root_path
|
43
|
+
else
|
44
|
+
return show_error "The tool was not launched successfully. Please try again."
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def configure
|
49
|
+
respond_to do |format|
|
50
|
+
format.xml do
|
51
|
+
render xml: Launch.xml_config(lti_launch_url)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
def show_error(message)
|
58
|
+
render text: message
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module LtiProvider
|
4
|
+
class Launch < ActiveRecord::Base
|
5
|
+
validates :canvas_url, :provider_params, :nonce, presence: true
|
6
|
+
|
7
|
+
attr_accessor :lti_errormsg
|
8
|
+
|
9
|
+
serialize :provider_params
|
10
|
+
|
11
|
+
def self.initialize_from_request(provider, request)
|
12
|
+
launch = new
|
13
|
+
|
14
|
+
launch.validate_provider(provider, request)
|
15
|
+
|
16
|
+
launch.provider_params = provider.to_params
|
17
|
+
launch.canvas_url = launch.api_endpoint(provider)
|
18
|
+
launch.nonce = launch.provider_params['oauth_nonce']
|
19
|
+
|
20
|
+
launch
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.xml_config(lti_launch_url)
|
24
|
+
tc = IMS::LTI::ToolConfig.new({
|
25
|
+
launch_url: lti_launch_url,
|
26
|
+
title: LtiProvider::XmlConfig.tool_title,
|
27
|
+
description: LtiProvider::XmlConfig.tool_description
|
28
|
+
})
|
29
|
+
|
30
|
+
tc.extend IMS::LTI::Extensions::Canvas::ToolConfig
|
31
|
+
platform = IMS::LTI::Extensions::Canvas::ToolConfig::PLATFORM
|
32
|
+
|
33
|
+
privacy_level = LtiProvider::XmlConfig.privacy_level || "public"
|
34
|
+
tc.send("canvas_privacy_#{privacy_level}!")
|
35
|
+
|
36
|
+
if LtiProvider::XmlConfig.tool_id
|
37
|
+
tc.set_ext_param(platform, :tool_id, LtiProvider::XmlConfig.tool_id)
|
38
|
+
end
|
39
|
+
|
40
|
+
if LtiProvider::XmlConfig.course_navigation
|
41
|
+
tc.canvas_course_navigation!(LtiProvider::XmlConfig.course_navigation.symbolize_keys)
|
42
|
+
end
|
43
|
+
|
44
|
+
if LtiProvider::XmlConfig.account_navigation
|
45
|
+
tc.canvas_account_navigation!(LtiProvider::XmlConfig.account_navigation.symbolize_keys)
|
46
|
+
end
|
47
|
+
|
48
|
+
if LtiProvider::XmlConfig.user_navigation
|
49
|
+
tc.canvas_user_navigation!(LtiProvider::XmlConfig.user_navigation.symbolize_keys)
|
50
|
+
end
|
51
|
+
|
52
|
+
if LtiProvider::XmlConfig.environments
|
53
|
+
tc.set_ext_param(platform, :environments, LtiProvider::XmlConfig.environments.symbolize_keys)
|
54
|
+
end
|
55
|
+
|
56
|
+
tc.to_xml(:indent => 2)
|
57
|
+
end
|
58
|
+
|
59
|
+
{
|
60
|
+
'context_label' => :course_name,
|
61
|
+
'custom_canvas_account_id' => :account_id,
|
62
|
+
'custom_canvas_course_id' => :course_id,
|
63
|
+
'custom_canvas_user_id' => :user_id,
|
64
|
+
'lis_person_name_full' => :user_name,
|
65
|
+
'roles' => :user_roles,
|
66
|
+
'tool_consumer_instance_guid' => :tool_consumer_instance_guid,
|
67
|
+
'user_image' => :user_avatar_url
|
68
|
+
}.each do |provider_param, method_name|
|
69
|
+
define_method(method_name) { provider_params[provider_param] }
|
70
|
+
end
|
71
|
+
|
72
|
+
def valid_provider?
|
73
|
+
!!@valid_provider
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate_provider(provider, request)
|
77
|
+
self.lti_errormsg =
|
78
|
+
if provider.consumer_key.blank?
|
79
|
+
"Consumer key not provided."
|
80
|
+
elsif provider.consumer_secret.blank?
|
81
|
+
"Consumer secret not configured on provider."
|
82
|
+
elsif !provider.valid_request?(request)
|
83
|
+
"The OAuth signature was invalid."
|
84
|
+
elsif oauth_timestamp_too_old?(provider.request_oauth_timestamp)
|
85
|
+
"Your request is too old."
|
86
|
+
end
|
87
|
+
|
88
|
+
@valid_provider = self.lti_errormsg.blank?
|
89
|
+
end
|
90
|
+
|
91
|
+
def api_endpoint(provider)
|
92
|
+
if provider.launch_presentation_return_url
|
93
|
+
uri = URI.parse(provider.launch_presentation_return_url)
|
94
|
+
domain = "#{uri.scheme}://#{uri.host}"
|
95
|
+
domain += ":#{uri.port}" unless uri.port.nil? || [80, 443].include?(uri.port.to_i)
|
96
|
+
return domain
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
def oauth_timestamp_too_old?(timestamp)
|
102
|
+
Time.now.utc.to_i - timestamp.to_i > 1.hour.to_i
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
default: &default
|
2
|
+
tool_title: 'Dummy App'
|
3
|
+
tool_description: 'A very handy dummy application for testing LtiProvider engine integration.'
|
4
|
+
tool_id: 'dummy'
|
5
|
+
|
6
|
+
# default: 'public'
|
7
|
+
privacy_level: 'public'
|
8
|
+
|
9
|
+
# url defaults to lti_launch_url, but can be overridden in each of these
|
10
|
+
course_navigation:
|
11
|
+
text: 'Dummy'
|
12
|
+
visibility: 'admins'
|
13
|
+
# account_navigation and user_navigation are also available with similar options
|
14
|
+
|
15
|
+
development:
|
16
|
+
<<: *default
|
17
|
+
|
18
|
+
test:
|
19
|
+
<<: *default
|
20
|
+
|
21
|
+
production:
|
22
|
+
<<: *default
|
data/config/routes.rb
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
LtiProvider::Engine.routes.draw do
|
2
|
+
match "/cookie_test", to: "lti#cookie_test", as: "cookie_test"
|
3
|
+
match "/consume_launch", to: "lti#consume_launch", as: "consume_launch"
|
4
|
+
match "/launch", to: "lti#launch", as: "lti_launch"
|
5
|
+
match "/configure(.:format)", to: "lti#configure", as: "lti_configure"
|
6
|
+
end
|