rack-lti 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.
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