rack-lti 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 277e9014df9050362c2e13d446193138204f4938
4
+ data.tar.gz: 8d0074eb73c29330d8fe0641fb9a16766c2b94c6
5
+ SHA512:
6
+ metadata.gz: 93e6f3179d41671d631a8238814220fefd789abb2814801dfcc580aeab170794d3bdfed0e1a044de11b3f09d7b24e04689c0d3964f4fcdd6e9f67c49b80f693e
7
+ data.tar.gz: 37a52e7d3499561ef840020ad1093a3910b0812dfcf233172b82d8b5a03c5f20521472b06c6780181b1e72dcb4ba233f8755e1dd35425b4b6c6a174ec178d307
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - jruby-19mode
6
+ - rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rack-lti.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Zach Pendleton
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,146 @@
1
+ # Rack::LTI
2
+
3
+ [![Build Status](https://travis-ci.org/zachpendleton/rack-lti.png)](https://travis-ci.org/zachpendleton/rack-lti)
4
+
5
+ Rack::LTI exposes LTI launch and config URLs in your Rack application, handling
6
+ authorization, storing launch parameters, and generating config information for
7
+ consumers.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'rack-lti'
14
+
15
+ ## Usage
16
+
17
+ Rack::LTI should work with any Rack-based app. This means Rails 3.x and
18
+ Sinatra, and probably whatever wonky framework you happen to be using.
19
+
20
+ Rack::LTI is tested on MRI Ruby 1.9 and 2.0, and the 1.9 branches of JRuby
21
+ and Rubinius. It will not work on any flavor of 1.8; upgrade already.
22
+
23
+ ### Rails 3
24
+
25
+ Add Rack::LTI to your `config/application.rb`:
26
+
27
+ ```ruby
28
+ class Application < Rails::Application
29
+ config.middleware.use Rack::LTI,
30
+ consumer_key: ->(key, consumer_id) { key == 'key_value' },
31
+ consumer_secret: ->(secret) { secret == 'top_secret' }
32
+
33
+ app_path: '/',
34
+ config_path: '/lti/config.xml',
35
+ launch_path: '/lti/launch',
36
+
37
+ title: 'My LTI App',
38
+ description: 'My LTI App description',
39
+
40
+ nonce_validator: ->(nonce) { !FakeNonceStore.include?(nonce) },
41
+ success: ->(params, session) {
42
+ params['launch_params'] = params unless session.nil?
43
+ },
44
+ time_limit: 60*60,
45
+
46
+ extensions: {
47
+ 'canvas.instructure.com' => {
48
+ course_navigation: {
49
+ default: 'enabled',
50
+ text: 'My LTI App'
51
+ }
52
+ }
53
+ },
54
+
55
+ custom_params: {
56
+ preferred_name: 'El Tigre Chino'
57
+ }
58
+ end
59
+ ```
60
+
61
+ ### Sinatra
62
+
63
+ Add Rack::LTI to your app:
64
+
65
+ ```ruby
66
+ class Application < Sinatra::Base
67
+ use Rack::LTI,
68
+ consumer_key: 'my_key',
69
+ consumer_secret: 'my_secret',
70
+
71
+ app_path: '/',
72
+ config_path: '/lti/config.xml',
73
+ launch_path: '/lti/launch',
74
+
75
+ title: 'My LTI App',
76
+ description: 'My LTI App description',
77
+
78
+ nonce_validator: ->(nonce) { !FakeNonceStore.include?(nonce) },
79
+ success: ->(params, session) {
80
+ params['launch_params'] = params unless session.nil?
81
+ },
82
+ time_limit: 60*60,
83
+
84
+ extensions: {
85
+ 'canvas.instructure.com' => {
86
+ course_navigation: {
87
+ default: 'enabled',
88
+ text: 'My LTI App'
89
+ }
90
+ }
91
+ },
92
+
93
+ custom_params: {
94
+ preferred_name: 'El Tigre Chino'
95
+ }
96
+ end
97
+ ```
98
+
99
+ ## Configuration
100
+
101
+ Rack::LTI takes either a configuration hash or block at initialization. Allowed
102
+ values are:
103
+
104
+ * `consumer_key` The consumer_key to check against the key given at launch.
105
+ This value can be a string or a lambda. If a lambda, it is passed the key
106
+ used by the consumer as well as their tool_consumer_instance_guid.
107
+ * `consumer_secret` The consumer_secret to check against the secret given at
108
+ launch. Like the consumer key, this value can be a string or a lambda. If a
109
+ lambda, it is passed the key and tool_consumer_instance_guid of the
110
+ consumer.
111
+ * `app_path` The path to redirect to on a successful launch. This should be
112
+ the main page of your application. Defaults to '/'.
113
+ * `config_path` The path to serve LTI config XML from. Defaults to
114
+ '/lti/config.xml'.
115
+ * `launch_path` The path to receive LTI launch requests at. Defaults to
116
+ '/lti/launch'.
117
+ * `title` The title of your LTI application.
118
+ * `description` The description of your LTI application.
119
+ * `nonce_validator` A lambda used to validate the current request's nonce.
120
+ It is passed the nonce to verify. If not provided, all nonces are allowed.
121
+ * `time_limit` The time limit, in seconds, to consider requests valid within.
122
+ If not passed, the default is 3600 seconds (one hour).
123
+ * `success` A lambda called on successful launch. It is passed the launch
124
+ params as a hash and the session if present. Can be used to cache params
125
+ for the current user, find the current user, etc. If not given, the launch
126
+ params are stored in the 'launch_params' key of the session.
127
+ * `extensions` A hash of extension information to include with the config.
128
+ Format is platform -> option -> properties. See usage examples above for
129
+ more detail.
130
+ * `custom_params` A hash of custom parameters to accept from the client. See
131
+ usage examples above for more detail.
132
+
133
+ ## About LTI
134
+
135
+ Interested in learning more about LTI? Here are some links to get you started:
136
+
137
+ * [Introduction to LTI](http://www.imsglobal.org/toolsinteroperability2.cfm)
138
+ * [1.1.1 Implementation Guide](http://www.imsglobal.org/LTI/v1p1p1/ltiIMGv1p1p1.html)
139
+
140
+ ## Contributing
141
+
142
+ 1. Fork it
143
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
144
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
145
+ 4. Push to the branch (`git push origin my-new-feature`)
146
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ task default: :test
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.pattern = 'test/**/*_test.rb'
8
+ end
@@ -0,0 +1,55 @@
1
+ require 'ims/lti'
2
+
3
+ module Rack::LTI
4
+ class Config < Hash
5
+ DEFAULT = {
6
+ app_path: '/',
7
+ config_path: '/lti/config.xml',
8
+ description: 'An LTI Application.',
9
+ launch_path: '/lti/launch',
10
+ nonce_validator: true,
11
+ success: ->(params, session) { session['launch_params'] = params if session },
12
+ time_limit: 60*60,
13
+ title: 'LTI App'
14
+ }
15
+
16
+ def initialize(options = {})
17
+ DEFAULT.merge(options).each { |k, v| self[k] = v }
18
+ instance_eval { yield(self) } if block_given?
19
+ end
20
+
21
+ [:consumer_key, :consumer_secret, :nonce_validator].each do |method|
22
+ define_method(method) do |*args|
23
+ if self[method].respond_to?(:call)
24
+ self[method].call(*args)
25
+ else
26
+ self[method]
27
+ end
28
+ end
29
+ end
30
+
31
+ def public?
32
+ self[:consumer_key].nil? && self[:consumer_secret].nil?
33
+ end
34
+
35
+ def to_xml(options = {})
36
+ # Stringify keys for IMS::LTI
37
+ config = self.merge(options).inject({}) do |h, v|
38
+ h[v[0].to_s] = v[1]
39
+ h
40
+ end
41
+
42
+ IMS::LTI::ToolConfig.new(config).to_xml(indent: 2)
43
+ end
44
+
45
+ def method_missing(method, *args, &block)
46
+ if method.match(/=$/)
47
+ self[method.to_s[0..-2].to_sym] = args.first
48
+ elsif self.has_key?(method)
49
+ self[method]
50
+ else
51
+ super
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,79 @@
1
+ require 'ims/lti'
2
+ require 'oauth/request_proxy/rack_request'
3
+ require 'rack/lti/config'
4
+
5
+ module Rack::LTI
6
+ class Middleware
7
+ attr_reader :app, :config
8
+
9
+ def initialize(app, options = {}, &block)
10
+ @app = app
11
+ @config = Config.new(options, &block)
12
+ end
13
+
14
+ def call(env)
15
+ request = Rack::Request.new(env)
16
+
17
+ if routes.has_key?(request.path)
18
+ env['rack.lti'] = true
19
+ send(routes[request.path], request, env)
20
+ else
21
+ @app.call(env)
22
+ end
23
+ end
24
+
25
+ def routes
26
+ {
27
+ @config.config_path => :config_action,
28
+ @config.launch_path => :launch_action
29
+ }
30
+ end
31
+
32
+ private
33
+
34
+ def config_action(request, env)
35
+ response = [@config.to_xml(launch_url: request.url.sub(@config.config_path, @config.launch_path))]
36
+ [200, { 'Content-Type' => 'application/xml', 'Content-Length' => response[0].length.to_s }, response]
37
+ end
38
+
39
+ def launch_action(request, env)
40
+ provider = IMS::LTI::ToolProvider.new(@config.consumer_key(*request.params.values_at('oauth_consumer_key', 'tool_consumer_instance_guid')),
41
+ @config.consumer_secret(*request.params.values_at('oauth_consumer_key', 'tool_consumer_instance_guid')),
42
+ request.params)
43
+
44
+ if valid?(provider, request)
45
+ @config.success.call(provider.to_params, env['rack.session'])
46
+ [301, { 'Content-Length' => '0', 'Content-Type' => 'text/html', 'Location' => @config.app_path }, []]
47
+ else
48
+ response = 'Invalid launch.'
49
+ [403, { 'Content-Type' => 'text/plain', 'Content-Length' => response.length.to_s }, [response]]
50
+ end
51
+ end
52
+
53
+ def valid?(provider, request)
54
+ valid_request?(provider, request) &&
55
+ valid_nonce?(request.params['oauth_nonce']) &&
56
+ valid_timestamp?(request.params['oauth_timestamp'].to_i)
57
+ end
58
+
59
+ def valid_request?(provider, request)
60
+ @config.public? ? true : provider.valid_request?(request)
61
+ end
62
+
63
+ def valid_nonce?(nonce)
64
+ if @config.nonce_validator.respond_to?(:call)
65
+ @config.nonce_validator.call(nonce)
66
+ else
67
+ @config.nonce_validator
68
+ end
69
+ end
70
+
71
+ def valid_timestamp?(timestamp)
72
+ if @config.time_limit.nil?
73
+ true
74
+ else
75
+ (Time.now.to_i - @config.time_limit) <= timestamp
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ module LTI
3
+ VERSION = '0.0.1'
4
+ end
5
+ end
data/lib/rack/lti.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'rack/lti/config'
2
+ require 'rack/lti/middleware'
3
+ require 'rack/lti/version'
4
+
5
+ module Rack
6
+ module LTI
7
+ def self.new(*args, &block)
8
+ Middleware.new(*args, &block)
9
+ end
10
+ end
11
+ end
data/lib/rack-lti.rb ADDED
@@ -0,0 +1 @@
1
+ require 'rack/lti'
data/rack-lti.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rack/lti/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'rack-lti'
8
+ spec.version = Rack::LTI::VERSION
9
+ spec.authors = ['Zach Pendleton']
10
+ spec.email = ['zachpendleton@gmail.com']
11
+ spec.description = <<-END
12
+ Rack::LTI provides LTI launch and configuration endpoints to your
13
+ Rack-based application. It handles configuration, authorization, and
14
+ routing.
15
+
16
+ For more information about LTI, see http://www.imsglobal.org/toolsinteroperability2.cfm.
17
+ END
18
+ spec.summary = %q{Middleware for handling LTI launches inside your Rack app.}
19
+ spec.homepage = 'https://github.com/zachpendleton/rack-lti'
20
+ spec.license = 'MIT'
21
+
22
+ spec.files = `git ls-files`.split($/)
23
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
24
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
25
+ spec.require_paths = ['lib']
26
+
27
+ spec.add_development_dependency 'bundler', '~> 1.3'
28
+ spec.add_development_dependency 'minitest', '~> 4.7.0'
29
+ spec.add_development_dependency 'rake'
30
+
31
+ spec.add_dependency 'ims-lti', '~> 1.1.2'
32
+ spec.add_dependency 'rack'
33
+ end
@@ -0,0 +1,112 @@
1
+ require 'minitest/autorun'
2
+ require 'rexml/document'
3
+ require 'rack/lti/config'
4
+
5
+ class ConfigTest < Minitest::Unit::TestCase
6
+ def setup
7
+ @config = Rack::LTI::Config.new
8
+ end
9
+
10
+ def test_config_accepts_hash_style_setters
11
+ @config[:setting] = 'value'
12
+ assert_equal 'value', @config[:setting]
13
+ end
14
+
15
+ def test_config_accepts_accessor_style_setters
16
+ @config.setting = 'value'
17
+ assert_equal 'value', @config.setting
18
+ end
19
+
20
+ def test_config_accepts_a_block
21
+ config = Rack::LTI::Config.new do |c|
22
+ c[:title] = 'custom title'
23
+ end
24
+
25
+ assert_equal 'custom title', config[:title]
26
+ end
27
+
28
+ def test_config_populates_default_values
29
+ assert_equal '/', @config.app_path
30
+ assert_equal '/lti/config.xml', @config.config_path
31
+ assert_equal 'An LTI Application.', @config.description
32
+ assert_equal '/lti/launch', @config.launch_path
33
+ assert_equal true, @config.nonce_validator
34
+ assert_equal 3600, @config.time_limit
35
+ assert_equal 'LTI App', @config.title
36
+ assert_instance_of Proc, @config.success
37
+ end
38
+
39
+ def test_consumer_key_returns_primitive_values
40
+ @config[:consumer_key] = 1
41
+ assert_equal 1, @config.consumer_key
42
+ end
43
+
44
+ def test_consumer_key_calls_a_proc_if_given
45
+ @config[:consumer_key] = ->(n) { n + 1 }
46
+ assert_equal 2, @config.consumer_key(1)
47
+ end
48
+
49
+ def test_consumer_secret_returns_primitive_values
50
+ @config[:consumer_secret] = 1
51
+ assert_equal 1, @config.consumer_secret
52
+ end
53
+
54
+ def test_consumer_secret_calls_a_proc_if_given
55
+ @config[:consumer_secret] = ->(n) { n + 1 }
56
+ assert_equal 2, @config.consumer_secret(1)
57
+ end
58
+
59
+ def test_nonce_validator_returns_primitive_values
60
+ @config[:nonce_validator] = 1
61
+ assert_equal 1, @config.nonce_validator
62
+ end
63
+
64
+ def test_nonce_validator_calls_a_proc_if_given
65
+ @config[:nonce_validator] = ->(n) { n + 1 }
66
+ assert_equal 2, @config.nonce_validator(1)
67
+ end
68
+
69
+ def test_public_returns_true_if_no_key_or_secret_is_set
70
+ @config[:consumer_key] = nil
71
+ @config[:consumer_secret] = nil
72
+ assert @config.public?
73
+ end
74
+
75
+ def test_to_xml_returns_an_xml_lti_config
76
+ body = REXML::Document.new(@config.to_xml(launch_url: 'http://example.com/launch'))
77
+
78
+ assert_equal @config.title,
79
+ REXML::XPath.match(body, '//blti:title').first.text
80
+ assert_equal @config.description,
81
+ REXML::XPath.match(body, '//blti:description').first.text
82
+ assert_equal 'http://example.com/launch',
83
+ REXML::XPath.match(body, '//blti:launch_url').first.text
84
+ end
85
+
86
+ def test_to_xml_includes_extensions
87
+ @config[:extensions] = {
88
+ 'canvas.instructure.com' => {
89
+ 'course_navigation' => {
90
+ 'privacy_level' => 'anonymous',
91
+ 'text' => 'Tool title',
92
+ 'url' => 'http://example.com'
93
+ }
94
+ }
95
+ }
96
+
97
+ body = REXML::Document.new(@config.to_xml(launch_url: 'http://example.com/launch'))
98
+ assert_equal 'anonymous',
99
+ REXML::XPath.match(body, '//lticm:property[@name="privacy_level"]').first.text
100
+ assert_equal 'Tool title',
101
+ REXML::XPath.match(body, '//lticm:property[@name="text"]').first.text
102
+ assert_equal 'http://example.com',
103
+ REXML::XPath.match(body, '//lticm:property[@name="url"]').first.text
104
+ end
105
+
106
+ def test_to_xml_includes_custom_params
107
+ @config[:custom_params] = { ck1: 'one', ck2: 'two' }
108
+ body = REXML::Document.new(@config.to_xml(launch_url: 'http://example.com/launch'))
109
+ assert_equal 'one', REXML::XPath.match(body, '//blti:custom/lticm:property[@name="ck1"]').first.text
110
+ assert_equal 'two', REXML::XPath.match(body, '//blti:custom/lticm:property[@name="ck2"]').first.text
111
+ end
112
+ end
data/test/lti_test.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'minitest/autorun'
2
+ require 'rack-lti'
3
+
4
+ class LtiTest < Minitest::Unit::TestCase
5
+ def setup
6
+ @app = ->(env) { [200, [], ['Hi']] }
7
+ end
8
+
9
+ def test_lti_proxies_new_calls_to_middleware
10
+ assert_instance_of Rack::LTI::Middleware, Rack::LTI.new(@app)
11
+ end
12
+ end
@@ -0,0 +1,142 @@
1
+ require 'minitest/autorun'
2
+ require 'rack'
3
+ require 'rack/lti/middleware'
4
+
5
+ class MiddlewareTest < Minitest::Unit::TestCase
6
+ def setup
7
+ @app = ->(env) { [200, {}, ['hi']] }
8
+ @lti_app = Rack::LTI::Middleware.new(@app)
9
+ @params = {
10
+ lti_message_type: 'basic-lti-launch-request',
11
+ lti_version: 'LTI-1p0',
12
+ resource_link_id: '88391-e1919-bb3456',
13
+ resource_link_title: 'Resource Title',
14
+ user_id: '0ae836b9-7fc9-4060-006f-27b2066ac545',
15
+ roles: 'instructor',
16
+ tool_consumer_instance_guid: 'guid',
17
+ oauth_consumer_key: 'key',
18
+ oauth_nonce: '12345',
19
+ oauth_timestamp: Time.now.to_i.to_s
20
+ }.reduce({}) { |m, h| m[h[0].to_s] = h[1]; m }
21
+ end
22
+
23
+ def test_middleware_accepts_an_app
24
+ assert_equal @lti_app.app, @app
25
+ end
26
+
27
+ def test_routes_returns_the_recognized_routes
28
+ known_routes = { @lti_app.config.config_path => :config_action,
29
+ @lti_app.config.launch_path => :launch_action }
30
+ assert_equal known_routes, @lti_app.routes
31
+ end
32
+
33
+ def test_call_returns_a_valid_rack_response
34
+ response = @lti_app.call(Rack::MockRequest.env_for('/'))
35
+
36
+ assert_equal response, @app.call(nil)
37
+ end
38
+
39
+ def test_call_intercepts_known_routes
40
+ env = Rack::MockRequest.env_for('/lti/launch')
41
+ @lti_app.call(env)
42
+
43
+ assert_equal true, env['rack.lti']
44
+ end
45
+
46
+ def test_call_ignores_unknown_routes
47
+ env = Rack::MockRequest.env_for('/')
48
+ @lti_app.call(env)
49
+
50
+ assert_equal nil, env['rack.lti']
51
+ end
52
+
53
+ def test_call_returns_403_on_invalid_launch
54
+ @lti_app.stub(:valid?, false) do
55
+ response = @lti_app.call(Rack::MockRequest.env_for('/lti/launch'))
56
+ assert_equal 403, response[0]
57
+ end
58
+ end
59
+
60
+ def test_call_returns_403_on_invalid_nonce
61
+ @lti_app.config.nonce_validator ->(nonce) { false }
62
+
63
+ @lti_app.stub(:valid_request?, true) do
64
+ response = @lti_app.call(Rack::MockRequest.env_for('/lti/launch'))
65
+ assert_equal 403, response[0]
66
+ end
67
+ end
68
+
69
+ def test_call_returns_403_on_expired_timestamp
70
+ @lti_app.config.nonce_validator = true
71
+ @lti_app.config.time_limit = 30
72
+
73
+ @lti_app.stub(:valid_request?, true) do
74
+ env = Rack::MockRequest.env_for('/lti/launch',
75
+ oauth_timestamp: Time.now - 60*60)
76
+ response = @lti_app.call(env)
77
+ assert_equal 403, response[0]
78
+ end
79
+ end
80
+
81
+ def test_call_stores_launch_params_in_the_session
82
+ @lti_app.stub(:valid_request?, true) do
83
+ env = Rack::MockRequest.env_for('/lti/launch', method: 'post',
84
+ 'rack.session' => {},
85
+ params: @params)
86
+ @lti_app.call(env)
87
+ assert_equal @params.keys.sort,
88
+ env['rack.session']['launch_params'].keys.sort
89
+ end
90
+ end
91
+
92
+ def test_call_redirects_to_app_path_on_success
93
+ @lti_app.stub(:valid_request?, true) do
94
+ env = Rack::MockRequest.env_for('/lti/launch', method: 'post',
95
+ params: @params)
96
+ response = @lti_app.call(env)
97
+ assert_equal 301, response[0]
98
+ assert_equal @lti_app.config[:app_path], response[1]['Location']
99
+ end
100
+ end
101
+
102
+ def test_call_succeeds_if_sessions_are_not_used
103
+ @lti_app.stub(:valid_request?, true) do
104
+ env = Rack::MockRequest.env_for('/lti/launch', method: 'post',
105
+ params: @params)
106
+ response = @lti_app.call(env)
107
+ assert_equal 301, response[0]
108
+ end
109
+ end
110
+
111
+ def test_call_returns_xml_when_config_path_is_used
112
+ response = @lti_app.call(Rack::MockRequest.env_for('/lti/config.xml'))
113
+ assert_equal 'application/xml', response[1]['Content-Type']
114
+ end
115
+
116
+ def test_consumer_key_is_passed_request_information
117
+ @lti_app.config[:consumer_key] = ->(key, guid) {
118
+ assert_equal 'key', key
119
+ assert_equal 'guid', guid
120
+ }
121
+
122
+ @lti_app.stub(:valid?, true) do
123
+ env = Rack::MockRequest.env_for('/lti/launch', method: 'post',
124
+ params: @params)
125
+ @lti_app.call(env)
126
+ end
127
+ end
128
+
129
+ def test_consumer_secret_is_passed_request_information
130
+ @lti_app.config[:consumer_secret] = ->(key, guid) {
131
+ assert_equal 'key', key
132
+ assert_equal 'guid', guid
133
+ }
134
+
135
+ @lti_app.stub(:valid?, true) do
136
+ env = Rack::MockRequest.env_for('/lti/launch', method: 'post',
137
+ params: @params)
138
+ @lti_app.call(env)
139
+ end
140
+ end
141
+
142
+ end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-lti
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Zach Pendleton
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-04-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 4.7.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 4.7.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: ims-lti
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 1.1.2
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 1.1.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: |2
84
+ Rack::LTI provides LTI launch and configuration endpoints to your
85
+ Rack-based application. It handles configuration, authorization, and
86
+ routing.
87
+
88
+ For more information about LTI, see http://www.imsglobal.org/toolsinteroperability2.cfm.
89
+ email:
90
+ - zachpendleton@gmail.com
91
+ executables: []
92
+ extensions: []
93
+ extra_rdoc_files: []
94
+ files:
95
+ - .gitignore
96
+ - .travis.yml
97
+ - Gemfile
98
+ - LICENSE.txt
99
+ - README.md
100
+ - Rakefile
101
+ - lib/rack-lti.rb
102
+ - lib/rack/lti.rb
103
+ - lib/rack/lti/config.rb
104
+ - lib/rack/lti/middleware.rb
105
+ - lib/rack/lti/version.rb
106
+ - rack-lti.gemspec
107
+ - test/config_test.rb
108
+ - test/lti_test.rb
109
+ - test/middleware_test.rb
110
+ homepage: https://github.com/zachpendleton/rack-lti
111
+ licenses:
112
+ - MIT
113
+ metadata: {}
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - '>='
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - '>='
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubyforge_project:
130
+ rubygems_version: 2.0.0
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: Middleware for handling LTI launches inside your Rack app.
134
+ test_files:
135
+ - test/config_test.rb
136
+ - test/lti_test.rb
137
+ - test/middleware_test.rb