dce_lti 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 93969389a33a1c7eabee7af327fd8f3b38df303e
4
- data.tar.gz: 9a042ba6f70ac543103a9c66087829f6db3cf672
3
+ metadata.gz: a4dcee8b8e745797874479ffb1d75eb6e468bd16
4
+ data.tar.gz: 844b534a13367e71716209aaf8818a74ff19c9aa
5
5
  SHA512:
6
- metadata.gz: e71bb0b7f2f05bc3f360f194857efa660dbf37a1e06fd14ad2b48198317ed64a0877fd696f6aa7151137a6074f027919c1ee0dcc204539aef550ce0632ecdf94
7
- data.tar.gz: e43d735ebe797fde5a6f78b0f5e81c8390bedba4df62c667fb44632e2cd732dd2ad638adc4ef5c6d5ebd58c66ded71bc48d155086f37e9f1a21813ed305f574b
6
+ metadata.gz: edebe996ebdddee116b497723a5958fd494027adfc4292e36eca9880737d84d6bb8ab12da1615be1605be6445fbc66a4b5737d6b2c4521d232b012c197ac5ddd
7
+ data.tar.gz: 60aec8cf3c206bf68891a781a125f8c5d66348dc734989a88a5d1e9a4d69b27152321e57ba9f1b92957ae87c3c0a92532faf62ed971ebdfd7fb05f4b21351046
data/README.md CHANGED
@@ -10,11 +10,18 @@ the [IMS::LTI gem](https://github.com/instructure/ims-lti).
10
10
 
11
11
  ## Getting started
12
12
 
13
- Add this engine to your gemfile:
13
+ Add these gems to your gemfile:
14
14
 
15
15
  gem 'dce_lti'
16
+ gem 'activerecord-session_store', '~> 0.1.1''
16
17
 
17
- Install it and run migrations:
18
+ Update (or create) `config/initializers/session_store.rb` and ensure it contains:
19
+
20
+ Rails.application.config.session_store :active_record_store, key: '_your_app_session', expire_after: 60.minutes
21
+
22
+ Where `_your_app_session` is your application's session key.
23
+
24
+ Bundle, install and then run migrations:
18
25
 
19
26
  bundle
20
27
  rake dce_lti:install
@@ -36,6 +43,10 @@ you have a valid LTI-provided user in `current_user`, thusly:
36
43
  end
37
44
  end
38
45
 
46
+ That's it! You'll need to configure to fit your use case, but you've got the
47
+ basics of LTI authentication (including experimental cookieless sessioning, see
48
+ below) working already.
49
+
39
50
  ## Configuration
40
51
 
41
52
  The generated config looks something like (commented defaults omitted):
@@ -105,7 +116,7 @@ See
105
116
  and other classes/modules under the IMS::LTI::Extensions hierarchy for further
106
117
  options.
107
118
 
108
- ## Notes
119
+ ## Other
109
120
 
110
121
  The `DceLti` provided controllers inherit from `ApplicationController` as
111
122
  defined in your application.
@@ -138,6 +149,95 @@ rendered. You can customize this output by creating a file named
138
149
  `app/views/dce_lti/sessions/invalid.html.erb`, per the default engine view
139
150
  resolution behavior.
140
151
 
152
+ ### Cookieless Sessions - Experimental
153
+
154
+ If you're running your LTI app on a domain different than your LMS, it will not
155
+ work in recent Safari browers. This is because [Safari blocks third party
156
+ cookies set in an iframe by
157
+ default](https://support.apple.com/kb/PH19214?locale=en_US). Mozilla has hinted
158
+ at implementing this default as well, so the days of setting a cookie in an
159
+ iframe and expecting it to work are probably numbered. Thanks, pervasive ad
160
+ networks!
161
+
162
+ There are a few options:
163
+
164
+ 1. Run your LMS and LTI provider on the same domain. This isn't really doable
165
+ if you want to provide a tool useful to multiple consumers on multiple domains,
166
+ 1. Only provide completely anonymous LTI tools,
167
+ 1. Build single page javascript-driven apps,
168
+ 1. Ask your users to enable third-party cookies,
169
+ 1. Block users when you detect they don't support third-party cookies,
170
+ 1. Persist the users session by including it in every link and form.
171
+
172
+ This engine implements the last option by detecting when a browser doesn't
173
+ accept cookies and then rewriting outgoing URLs and forms to include a session.
174
+
175
+ This behavior is disabled by default and requires minimal app-level changes:
176
+
177
+ 1. Edit `config/initializers/dce_lti_config.rb`. Uncomment
178
+ `lti.enable_cookieless_sessions = false` and set it to `true`.
179
+ 1. You must use database sessions as provided by `activerecord-session_store`,
180
+ which we install by default and you should've already configured.
181
+ 1. The `redirect_after_successful_auth` path must include the session key and
182
+ id so we can pick it up if cookies aren't available (this is the default as
183
+ well).
184
+
185
+ Please report bugs to github issues, there are bound to be a few.
186
+
187
+ If a user supports cookies, we do basically nothing. We don't rewrite forms or
188
+ URLs and we use a cookied (and database-backed) session per the usual.
189
+
190
+ #### How cookieless sessions work
191
+
192
+ When a request comes in without a cookie but with the session key and ID, then
193
+ the `DceLti::Middleware::CookieShim` middleware "shims" it into the Rack
194
+ environment and the session information is restored by subsequent middleware.
195
+
196
+ When we detect that a user doesn't accept third-party cookies, we use
197
+ `Rack::Plastic` to rewrite forms and URLs to include the session key and id
198
+ from the `redirect_after_successful_auth` redirect. This happens in the
199
+ `DceLti::Middleware::CookielessSessions` middleware.
200
+
201
+ #### Known issues with cookieless sessions
202
+
203
+ * Even if your app works with cookieless sessions, other cookie sessioned
204
+ iframe'd apps won't: for instance the youtube javascript iframe API and many
205
+ other third-party javascript apps.
206
+ * We only rewrite URLs without a protocol and domain ('/posts/1') to match the
207
+ URLs emitted by rails by default. If you're manually inserting links to your
208
+ application that include the protocol and domain name
209
+ ('http://example.com/posts/1'), the middleware doesn't catch it. This could
210
+ be fixed to be a bit smarter in the future.
211
+ * You will need to ensure that the session key and id tags along for ajax
212
+ requests to your LTI application.
213
+
214
+ #### Database session cleanup
215
+
216
+ Run the included `dce_lti:clean_sessions` rake task periodically to remove old
217
+ sessions - the default is 7 days, you can modify this with the
218
+ `OLDER_THAN_X_DAYS` environment variable, thusly:
219
+
220
+ OLDER_THAN_X_DAYS=14 rake dce_lti:clean_sessions
221
+
222
+ #### Database session hijacking for cookieless sessions
223
+
224
+ This is an issue, unfortunately. If a malicious user were able to get ahold of
225
+ a link in another user's LTI session (when that other user is under a
226
+ cookieless session) it'd contain a working session ID and could be exploited.
227
+
228
+ This can be mitigated several ways:
229
+
230
+ 1. Deliver your LTI application over SSL to protect the transport layer. You
231
+ pretty much need to do this anyway, so this shouldn't be a big deal.
232
+ 1. Expire your sessions by setting the `expire_after` option in
233
+ `config/initializers/session_store.rb` to a value short enough to not annoy
234
+ your users.
235
+
236
+ If you set `expire_after` too short, your users will get annoyed. If you set it
237
+ too long, the sessions will linger and increase the time the session is
238
+ vulnerable. We're looking into other ways of mitigating this as well - PRs
239
+ accepted!
240
+
141
241
  ### Nonce cleanup
142
242
 
143
243
  You can clean up lti-related
@@ -0,0 +1,12 @@
1
+ class AddSessionsTable < ActiveRecord::Migration
2
+ def change
3
+ create_table :sessions do |t|
4
+ t.string :session_id, :null => false
5
+ t.text :data
6
+ t.timestamps
7
+ end
8
+
9
+ add_index :sessions, :session_id, :unique => true
10
+ add_index :sessions, :updated_at
11
+ end
12
+ end
@@ -14,5 +14,10 @@ module DceLti
14
14
  User.find_by(id: session[:current_user_id])
15
15
  end
16
16
  end
17
+
18
+ def cookieless_session?
19
+ cookie = env.fetch('HTTP_COOKIE', '')
20
+ cookie.blank? || cookie.match(/shimmed_cookie/)
21
+ end
17
22
  end
18
23
  end
@@ -12,20 +12,34 @@ resource_link_id
12
12
  resource_link_title
13
13
  tool_consumer_instance_guid
14
14
  launch_presentation_return_url
15
- |
15
+ |
16
+
17
+ config.enable_cookieless_sessions = false
16
18
 
17
19
  config.provider_title = (ENV['LTI_PROVIDER_TITLE'] || 'DCE LTI Provider')
18
20
  config.provider_description = (ENV['LTI_PROVIDER_DESCRIPTION'] || 'A description of this')
19
21
 
20
22
  config.redirect_after_successful_auth = -> (controller) do
21
- Rails.application.routes.url_helpers.root_path
23
+ session_key_name = Rails.application.config.session_options[:key]
24
+ Rails.application.routes.url_helpers.root_path(session_key_name => controller.session.id)
22
25
  end
26
+
23
27
  config.tool_config_extensions = ->(*) {}
24
28
  yield config
25
29
  end
26
30
 
27
31
  initializer 'dce_lti.load_helpers' do
28
32
  ActionController::Base.send :include, ControllerMethods
33
+ ActionController::Base.send :include, RedirectToHelper
34
+ ActionController::Base.send :helper, RedirectToHelper
35
+ ApplicationController.skip_before_filter :verify_authenticity_token, if: :cookieless_session?
36
+ end
37
+
38
+ initializer 'dce_lti.add_middleware' do |app|
39
+ if config.enable_cookieless_sessions
40
+ app.middleware.insert_before ActionDispatch::Cookies, 'DceLti::Middleware::CookieShim'
41
+ app.middleware.use 'DceLti::Middleware::CookielessSessions'
42
+ end
29
43
  end
30
44
 
31
45
  isolate_namespace DceLti
@@ -0,0 +1,36 @@
1
+ module DceLti
2
+ module Middleware
3
+ class CookieShim
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ if env['HTTP_COOKIE'].to_s.strip.empty?
10
+ params = parse_query_string(env)
11
+ if params[session_key_name.to_s]
12
+ env['HTTP_COOKIE'] = "#{session_key_name}=#{params[session_key_name.to_s]};shimmed_cookie=1"
13
+ end
14
+ end
15
+
16
+ @app.call(env)
17
+ end
18
+
19
+ private
20
+
21
+ def parse_query_string(env)
22
+ query_string = env['QUERY_STRING']
23
+ params = {}
24
+ query_string.split('&').each do |parameter|
25
+ (key, value) = parameter.split('=')
26
+ params[key] = value
27
+ end
28
+ params
29
+ end
30
+
31
+ def session_key_name
32
+ @session_key_name ||= Rails.application.config.session_options[:key]
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,79 @@
1
+ require 'rack-plastic'
2
+
3
+ module DceLti
4
+ module Middleware
5
+ class CookielessSessions < Rack::Plastic
6
+ def change_nokogiri_doc(doc)
7
+ if no_cookies? || shimmed_cookie?
8
+ doc.css('a').each do |a|
9
+ href = a[:href]
10
+
11
+ next unless local_url?(href)
12
+ next if url_has_key_already?(href)
13
+
14
+ if href.match(/\?/)
15
+ a[:href] += "&#{session_key_name}=#{session_id}"
16
+ else
17
+ a[:href] += "?#{session_key_name}=#{session_id}"
18
+ end
19
+ end
20
+
21
+ doc.css('form').each do |form|
22
+ action = form[:action]
23
+ next unless local_url?(action)
24
+ next if url_has_key_already?(action)
25
+
26
+ # For PATCH, PUT, DELETE and POST, which allow
27
+ # params mixed in the action and the form.
28
+ if action.match(/\?/)
29
+ form[:action] += "&#{session_key_name}=#{session_id}"
30
+ else
31
+ form[:action] += "?#{session_key_name}=#{session_id}"
32
+ end
33
+
34
+ # For GET, oddly. GET method forms stomp all params encoded
35
+ # in the action
36
+ input_node = Nokogiri::XML::Node.new('input', doc)
37
+ input_node[:type] = 'hidden'
38
+ input_node[:name] = session_key_name
39
+ input_node[:value] = session_id
40
+ form.children.first.add_previous_sibling(
41
+ input_node
42
+ )
43
+ end
44
+ end
45
+ doc
46
+ end
47
+
48
+ private
49
+
50
+ def shimmed_cookie?
51
+ @p.request.env['HTTP_COOKIE'].to_s.strip.match(/shimmed_cookie/)
52
+ end
53
+
54
+ def no_cookies?
55
+ @p.request.env['HTTP_COOKIE'].to_s.strip.empty?
56
+ end
57
+
58
+ def local_url?(url)
59
+ ! url.match(/\Ahttps?:\/\/|\/\//i)
60
+ end
61
+
62
+ def url_has_key_already?(url)
63
+ url.match(/#{session_key_name}/i)
64
+ end
65
+
66
+ def session_key_name
67
+ @session_key_name ||= Rails.application.config.session_options[:key]
68
+ end
69
+
70
+ def session
71
+ @p.request.env['rack.session']
72
+ end
73
+
74
+ def session_id
75
+ session.id
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,25 @@
1
+ module DceLti
2
+ module RedirectToHelper
3
+ def redirect_to(options)
4
+ session_key_name = Rails.application.config.session_options[:key]
5
+ if request.env.fetch('HTTP_COOKIE', '').match(/shimmed_cookie/) &&
6
+ (::DceLti::Engine.config.enable_cookieless_sessions)
7
+ case options
8
+ when Hash
9
+ options.merge!(session_key_name => session.id)
10
+ when String
11
+ if options.match(/\?/)
12
+ unless options.match(/#{session_key_name}/)
13
+ options += %Q|&#{session_key_name}=#{session.id}|
14
+ end
15
+ else
16
+ unless options.match(/#{session_key_name}/)
17
+ options += %Q|?#{session_key_name}=#{session.id}|
18
+ end
19
+ end
20
+ end
21
+ end
22
+ super(options)
23
+ end
24
+ end
25
+ end
@@ -1,3 +1,3 @@
1
1
  module DceLti
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
data/lib/dce_lti.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  require "dce_lti/engine"
2
2
  require 'dce_lti/controller_methods'
3
+ require 'dce_lti/redirect_to_helper'
4
+ require 'dce_lti/middleware/cookie_shim'
5
+ require 'dce_lti/middleware/cookieless_sessions'
3
6
 
4
7
  module DceLti
5
8
  end
@@ -31,4 +31,11 @@ engine provides.
31
31
  task clean_nonces: :environment do
32
32
  DceLti::Nonce.clean
33
33
  end
34
+
35
+ desc 'Clean up old sessions'
36
+ task clean_sessions: :environment do
37
+ older_than = (ENV.fetch('OLDER_THAN_X_DAYS', 7)).to_i
38
+ session_klass = ActionDispatch::Session::ActiveRecordStore.session_class
39
+ session_klass.where('updated_at < ?', (Time.now - older_than.days)).delete_all
40
+ end
34
41
  end
@@ -5,7 +5,21 @@ DceLti::Engine.setup do |lti|
5
5
  #
6
6
  # lti.provider_title = (ENV['LTI_PROVIDER_TITLE'] || 'DCE LTI Provider')
7
7
  # lti.provider_description = (ENV['LTI_PROVIDER_DESCRIPTION'] || 'A description of this')
8
- # lti.redirect_after_successful_auth = ->(controller) { Rails.application.routes.url_helpers.root_path }
8
+ #
9
+ # Set this to `true` to enable the form and URL-rewriting behavior that
10
+ # allows for the creation of cookieless sessions. The default is `false`,
11
+ # meaning we don't attempt to use cookieless sessions when a cookie cannot be
12
+ # set - the session just fails.
13
+ #
14
+ # lti.enable_cookieless_sessions = false
15
+ #
16
+ # The default post-auth redirect includes the session key and session id so
17
+ # that we can instantiate a successful cookieless session if needed.
18
+ #
19
+ # lti.redirect_after_successful_auth = ->(controller) {
20
+ # session_key_name = Rails.application.config.session_options[:key]
21
+ # Rails.application.routes.url_helpers.root_path(session_key_name => controller.session.id)
22
+ # }
9
23
 
10
24
  lti.consumer_secret = (ENV['LTI_CONSUMER_SECRET'] || 'consumer_secret')
11
25
  lti.consumer_key = (ENV['LTI_CONSUMER_KEY'] || 'consumer_key')
@@ -2983,3 +2983,69 @@ Completed 200 OK in 3ms (Views: 0.4ms | ActiveRecord: 0.6ms)
2983
2983
   (0.2ms) SELECT version FROM "schema_migrations"
2984
2984
   (1.2ms) INSERT INTO "schema_migrations" (version) VALUES ('20141008172001')
2985
2985
   (1.1ms) INSERT INTO "schema_migrations" (version) VALUES ('20141003180140')
2986
+  (105.5ms) DROP DATABASE IF EXISTS "dce_lti_dummy_test"
2987
+  (241.9ms) CREATE DATABASE "dce_lti_dummy_test" ENCODING = 'utf8'
2988
+ SQL (0.1ms) CREATE EXTENSION IF NOT EXISTS "plpgsql"
2989
+  (13.1ms) CREATE TABLE "dce_lti_nonces" ("id" serial primary key, "nonce" character varying(255), "created_at" timestamp, "updated_at" timestamp)
2990
+  (4.4ms) CREATE UNIQUE INDEX "index_dce_lti_nonces_on_nonce" ON "dce_lti_nonces" USING btree ("nonce")
2991
+  (6.6ms) CREATE TABLE "dce_lti_users" ("id" serial primary key, "lti_user_id" character varying(255), "lis_person_contact_email_primary" character varying(255), "lis_person_name_family" character varying(255), "lis_person_name_full" character varying(255), "lis_person_name_given" character varying(255), "lis_person_sourcedid" character varying(255), "user_image" character varying(255), "roles" character varying(255)[] DEFAULT '{}', "created_at" timestamp, "updated_at" timestamp)
2992
+  (1.4ms) CREATE TABLE "schema_migrations" ("version" character varying(255) NOT NULL) 
2993
+  (3.4ms) CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version")
2994
+  (0.3ms) SELECT version FROM "schema_migrations"
2995
+  (1.2ms) INSERT INTO "schema_migrations" (version) VALUES ('20141008172001')
2996
+  (1.0ms) INSERT INTO "schema_migrations" (version) VALUES ('20141003180140')
2997
+  (205.8ms) DROP DATABASE IF EXISTS "dce_lti_dummy_test"
2998
+  (227.7ms) CREATE DATABASE "dce_lti_dummy_test" ENCODING = 'utf8'
2999
+ SQL (0.1ms) CREATE EXTENSION IF NOT EXISTS "plpgsql"
3000
+  (7.5ms) CREATE TABLE "dce_lti_nonces" ("id" serial primary key, "nonce" character varying(255), "created_at" timestamp, "updated_at" timestamp)
3001
+  (3.5ms) CREATE UNIQUE INDEX "index_dce_lti_nonces_on_nonce" ON "dce_lti_nonces" USING btree ("nonce")
3002
+  (13.6ms) CREATE TABLE "dce_lti_users" ("id" serial primary key, "lti_user_id" character varying(255), "lis_person_contact_email_primary" character varying(255), "lis_person_name_family" character varying(255), "lis_person_name_full" character varying(255), "lis_person_name_given" character varying(255), "lis_person_sourcedid" character varying(255), "user_image" character varying(255), "roles" character varying(255)[] DEFAULT '{}', "created_at" timestamp, "updated_at" timestamp)
3003
+  (2.7ms) CREATE TABLE "schema_migrations" ("version" character varying(255) NOT NULL) 
3004
+  (7.0ms) CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version")
3005
+  (0.2ms) SELECT version FROM "schema_migrations"
3006
+  (2.3ms) INSERT INTO "schema_migrations" (version) VALUES ('20141008172001')
3007
+  (2.2ms) INSERT INTO "schema_migrations" (version) VALUES ('20141003180140')
3008
+  (104.9ms) DROP DATABASE IF EXISTS "dce_lti_dummy_test"
3009
+  (233.8ms) CREATE DATABASE "dce_lti_dummy_test" ENCODING = 'utf8'
3010
+ SQL (0.2ms) CREATE EXTENSION IF NOT EXISTS "plpgsql"
3011
+  (8.3ms) CREATE TABLE "dce_lti_nonces" ("id" serial primary key, "nonce" character varying(255), "created_at" timestamp, "updated_at" timestamp)
3012
+  (3.3ms) CREATE UNIQUE INDEX "index_dce_lti_nonces_on_nonce" ON "dce_lti_nonces" USING btree ("nonce")
3013
+  (7.5ms) CREATE TABLE "dce_lti_users" ("id" serial primary key, "lti_user_id" character varying(255), "lis_person_contact_email_primary" character varying(255), "lis_person_name_family" character varying(255), "lis_person_name_full" character varying(255), "lis_person_name_given" character varying(255), "lis_person_sourcedid" character varying(255), "user_image" character varying(255), "roles" character varying(255)[] DEFAULT '{}', "created_at" timestamp, "updated_at" timestamp)
3014
+  (1.5ms) CREATE TABLE "schema_migrations" ("version" character varying(255) NOT NULL) 
3015
+  (7.3ms) CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version")
3016
+  (0.5ms) SELECT version FROM "schema_migrations"
3017
+  (2.4ms) INSERT INTO "schema_migrations" (version) VALUES ('20141008172001')
3018
+  (2.6ms) INSERT INTO "schema_migrations" (version) VALUES ('20141003180140')
3019
+  (205.3ms) DROP DATABASE IF EXISTS "dce_lti_dummy_test"
3020
+  (229.4ms) CREATE DATABASE "dce_lti_dummy_test" ENCODING = 'utf8'
3021
+ SQL (0.2ms) CREATE EXTENSION IF NOT EXISTS "plpgsql"
3022
+  (16.2ms) CREATE TABLE "dce_lti_nonces" ("id" serial primary key, "nonce" character varying(255), "created_at" timestamp, "updated_at" timestamp)
3023
+  (4.8ms) CREATE UNIQUE INDEX "index_dce_lti_nonces_on_nonce" ON "dce_lti_nonces" USING btree ("nonce")
3024
+  (7.7ms) CREATE TABLE "dce_lti_users" ("id" serial primary key, "lti_user_id" character varying(255), "lis_person_contact_email_primary" character varying(255), "lis_person_name_family" character varying(255), "lis_person_name_full" character varying(255), "lis_person_name_given" character varying(255), "lis_person_sourcedid" character varying(255), "user_image" character varying(255), "roles" character varying(255)[] DEFAULT '{}', "created_at" timestamp, "updated_at" timestamp)
3025
+  (1.3ms) CREATE TABLE "schema_migrations" ("version" character varying(255) NOT NULL) 
3026
+  (3.3ms) CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version")
3027
+  (0.3ms) SELECT version FROM "schema_migrations"
3028
+  (1.0ms) INSERT INTO "schema_migrations" (version) VALUES ('20141008172001')
3029
+  (1.0ms) INSERT INTO "schema_migrations" (version) VALUES ('20141003180140')
3030
+  (107.1ms) DROP DATABASE IF EXISTS "dce_lti_dummy_test"
3031
+  (329.4ms) CREATE DATABASE "dce_lti_dummy_test" ENCODING = 'utf8'
3032
+ SQL (0.2ms) CREATE EXTENSION IF NOT EXISTS "plpgsql"
3033
+  (15.0ms) CREATE TABLE "dce_lti_nonces" ("id" serial primary key, "nonce" character varying(255), "created_at" timestamp, "updated_at" timestamp)
3034
+  (7.1ms) CREATE UNIQUE INDEX "index_dce_lti_nonces_on_nonce" ON "dce_lti_nonces" USING btree ("nonce")
3035
+  (15.3ms) CREATE TABLE "dce_lti_users" ("id" serial primary key, "lti_user_id" character varying(255), "lis_person_contact_email_primary" character varying(255), "lis_person_name_family" character varying(255), "lis_person_name_full" character varying(255), "lis_person_name_given" character varying(255), "lis_person_sourcedid" character varying(255), "user_image" character varying(255), "roles" character varying(255)[] DEFAULT '{}', "created_at" timestamp, "updated_at" timestamp)
3036
+  (2.6ms) CREATE TABLE "schema_migrations" ("version" character varying(255) NOT NULL) 
3037
+  (7.6ms) CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version")
3038
+  (0.3ms) SELECT version FROM "schema_migrations"
3039
+  (2.6ms) INSERT INTO "schema_migrations" (version) VALUES ('20141008172001')
3040
+  (2.3ms) INSERT INTO "schema_migrations" (version) VALUES ('20141003180140')
3041
+  (106.1ms) DROP DATABASE IF EXISTS "dce_lti_dummy_test"
3042
+  (233.4ms) CREATE DATABASE "dce_lti_dummy_test" ENCODING = 'utf8'
3043
+ SQL (0.4ms) CREATE EXTENSION IF NOT EXISTS "plpgsql"
3044
+  (10.2ms) CREATE TABLE "dce_lti_nonces" ("id" serial primary key, "nonce" character varying(255), "created_at" timestamp, "updated_at" timestamp)
3045
+  (7.4ms) CREATE UNIQUE INDEX "index_dce_lti_nonces_on_nonce" ON "dce_lti_nonces" USING btree ("nonce")
3046
+  (15.2ms) CREATE TABLE "dce_lti_users" ("id" serial primary key, "lti_user_id" character varying(255), "lis_person_contact_email_primary" character varying(255), "lis_person_name_family" character varying(255), "lis_person_name_full" character varying(255), "lis_person_name_given" character varying(255), "lis_person_sourcedid" character varying(255), "user_image" character varying(255), "roles" character varying(255)[] DEFAULT '{}', "created_at" timestamp, "updated_at" timestamp)
3047
+  (2.6ms) CREATE TABLE "schema_migrations" ("version" character varying(255) NOT NULL) 
3048
+  (6.8ms) CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version")
3049
+  (0.2ms) SELECT version FROM "schema_migrations"
3050
+  (2.1ms) INSERT INTO "schema_migrations" (version) VALUES ('20141008172001')
3051
+  (2.1ms) INSERT INTO "schema_migrations" (version) VALUES ('20141003180140')