coalescing_panda 4.7.0 → 5.0.1

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: 943b18616d0a5e87a50358017ce3faf8e27fb7ff
4
- data.tar.gz: 10539ffb27a3ae9709b58e9f08b3e1bd57e4f295
3
+ metadata.gz: 4946cde6027861484869379aa63fb8c0016d413d
4
+ data.tar.gz: 40794a3d7beab2e690e38da0d0433b0195ab2c60
5
5
  SHA512:
6
- metadata.gz: 5a2df63567b03a1b07d05efa93676139f470b1cc625cdd2887ccd38df80f0f08f0b04eb0cf676ae7ad2cb7b515477f069c9bdb748810ebede82f6a14203227b6
7
- data.tar.gz: 414e8d88d43b97ed4564d6ca67bc60e4c38b0638e01fc8f4a316a26b586009ce4f7136080a07319948f9835456c7642db675ee2c70e21f74fb96b909dcd1f990
6
+ metadata.gz: 0672ab922b73f33f81534f586e918f8c82dec08c14f30e56d1f97d62c0597416529f8f96baab489bf3d9c6c7a114563ff513ae3fa7afd47a15f9faddac2e5695
7
+ data.tar.gz: 99afede25863235a5db2dfaf59e2ef7d64bf7361243fead2ddb5dce5e0b01db9b73c74c039e65c01afd21956caf0f07abda7ac106ecf9a37b8e782c7113d1667
@@ -7,6 +7,8 @@ module CoalescingPanda
7
7
  end
8
8
 
9
9
  def redirect
10
+ use_secure_headers_override(:allow_inline_scripts)
11
+
10
12
  if !params[:error] && retrieve_oauth_state
11
13
  lti_account = LtiAccount.find_by_key(@oauth_state.data[:key])
12
14
  client_id = lti_account.oauth2_client_id
@@ -0,0 +1,38 @@
1
+ module CoalescingPanda
2
+ class PersistentSession < ActiveRecord::Base
3
+ serialize :data, Hash
4
+ belongs_to :coalescing_panda_lti_account, :class_name => 'CoalescingPanda::LtiAccount'
5
+ validates :coalescing_panda_lti_account_id, presence: true
6
+
7
+ after_initialize do
8
+ self.session_key ||= SecureRandom.urlsafe_base64(60)
9
+ end
10
+
11
+ def self.create_from_launch(launch_params, account_id)
12
+ session = PersistentSession.new(coalescing_panda_lti_account_id: account_id)
13
+ session.data[:launch_params] = launch_params.to_unsafe_h.with_indifferent_access
14
+ session.data[:roles] = launch_params['roles'].split(',').map { |role|
15
+ case role.downcase.strip
16
+ when 'admin'
17
+ :admin
18
+ when 'urn:lti:instrole:ims/lis/administrator'
19
+ :admin
20
+ when 'learner'
21
+ :student
22
+ when 'instructor'
23
+ :teacher
24
+ when 'urn:lti:role:ims/lis/teachingassistant'
25
+ :ta
26
+ when 'contentdeveloper'
27
+ :designer
28
+ when 'urn:lti:instrole:ims/lis/observer'
29
+ :observer
30
+ else
31
+ :none
32
+ end
33
+ }.uniq
34
+ session.save!
35
+ session
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,13 @@
1
+ class CreateCoalescingPandaPersistentSession < ActiveRecord::Migration
2
+ def change
3
+ create_table :coalescing_panda_persistent_sessions do |t|
4
+ t.string :session_key
5
+ t.text :data
6
+ t.integer :coalescing_panda_lti_account_id
7
+
8
+ t.timestamps null: false
9
+ end
10
+ add_index :coalescing_panda_persistent_sessions, :session_key, unique: true
11
+ add_index :coalescing_panda_persistent_sessions, :coalescing_panda_lti_account_id, name: 'index_persistent_session_on_lti_account_id'
12
+ end
13
+ end
@@ -2,6 +2,33 @@ require 'browser'
2
2
 
3
3
  module CoalescingPanda
4
4
  module ControllerHelpers
5
+ def current_session
6
+ @current_session ||= CoalescingPanda::PersistentSession.find_by(session_key: session_key) if session_key
7
+ @current_session ||= CoalescingPanda::PersistentSession.create_from_launch(params, current_lti_account.id)
8
+ @current_session
9
+ end
10
+
11
+ def current_lti_account
12
+ @account ||= CoalescingPanda::LtiAccount.find_by!(key: organization_key) if organization_key
13
+ @account ||= CoalescingPanda::LtiAccount.find_by(id: organization_id) if organization_id
14
+ @account
15
+ end
16
+
17
+ def current_session_data
18
+ current_session.data
19
+ end
20
+
21
+ def encrypted_session_key
22
+ msg_encryptor.encrypt_and_sign(current_session.session_key)
23
+ end
24
+
25
+ def save_session
26
+ current_session.try(:save)
27
+ end
28
+
29
+ def session_changed?
30
+ current_session.changed? && current_session.changes[:data].present?
31
+ end
5
32
 
6
33
  def canvas_oauth2(*roles)
7
34
  return if have_session?
@@ -100,18 +127,19 @@ module CoalescingPanda
100
127
 
101
128
  def lti_authorize!(*roles)
102
129
  authorized = false
103
- if @lti_account = params['oauth_consumer_key'] && LtiAccount.find_by_key(params['oauth_consumer_key'])
130
+ if (@lti_account = params['oauth_consumer_key'] && LtiAccount.find_by_key(params['oauth_consumer_key']))
104
131
  sanitized_params = sanitize_params
105
132
  authenticator = IMS::LTI::Services::MessageAuthenticator.new(request.original_url, sanitized_params, @lti_account.secret)
106
133
  authorized = authenticator.valid_signature?
107
134
  end
108
- logger.info 'not authorized on tp valid request' if !authorized
135
+ logger.info 'not authorized on tp valid request' unless authorized
109
136
  authorized = authorized && (roles.count == 0 || (roles & lti_roles).count > 0)
110
- logger.info 'not authorized on roles' if !authorized
137
+ logger.info 'not authorized on roles' unless authorized
111
138
  authorized = authorized && @lti_account.validate_nonce(params['oauth_nonce'], DateTime.strptime(params['oauth_timestamp'], '%s'))
112
- logger.info 'not authorized on nonce' if !authorized
139
+ logger.info 'not authorized on nonce' unless authorized
113
140
  render :text => 'Invalid Credentials, please contact your Administrator.', :status => :unauthorized unless authorized
114
- authorized = authorized && check_for_iframes_problem if authorized
141
+ # create session on first launch
142
+ current_session
115
143
  authorized
116
144
  end
117
145
 
@@ -136,26 +164,7 @@ module CoalescingPanda
136
164
  end
137
165
 
138
166
  def lti_roles
139
- @lti_roles ||= params['roles'].split(',').map { |role|
140
- case role.downcase.strip
141
- when 'admin'
142
- :admin
143
- when 'urn:lti:instrole:ims/lis/administrator'
144
- :admin
145
- when 'learner'
146
- :student
147
- when 'instructor'
148
- :teacher
149
- when 'urn:lti:role:ims/lis/teachingassistant'
150
- :ta
151
- when 'contentdeveloper'
152
- :designer
153
- when 'urn:lti:instrole:ims/lis/observer'
154
- :observer
155
- else
156
- :none
157
- end
158
- }.uniq
167
+ @lti_roles ||= current_session_data[:roles]
159
168
  end
160
169
 
161
170
  def canvas_environment
@@ -168,38 +177,68 @@ module CoalescingPanda
168
177
  end
169
178
 
170
179
  def session_check
171
- logger.warn 'session_check is deprecated. Functionality moved to canvas_oauth2.'
180
+ logger.warn 'session_check is deprecated. Functionality moved to lti_authorize.'
172
181
  end
173
182
 
174
- def check_for_iframes_problem
175
- if cookies_need_iframe_fix?
176
- fix_iframe_cookies
177
- return false
178
- end
183
+ private
184
+
185
+ def msg_encryptor
186
+ @crypt ||= ActiveSupport::MessageEncryptor.new(Rails.application.secrets.secret_key_base[0..31])
187
+ end
188
+
189
+ def organization_key
190
+ params[:oauth_consumer_key] || (current_session_data[:launch_params][:oauth_consumer_key] if @current_session)
191
+ end
192
+
193
+ def organization_id
194
+ params[:organization_id] || (current_session_data[:launch_params][:organization_id] if @current_session)
195
+ end
179
196
 
180
- # For safari we may have been launched temporarily full-screen by canvas. This allows us to set the session cookie.
181
- # In this case, we should make sure the session cookie is fixed and redirect back to canvas to properly launch the embedded LTI.
182
- if params[:platform_redirect_url]
183
- session[:safari_cookie_fixed] = true
184
- redirect_to params[:platform_redirect_url]
185
- return false
197
+ def session_key
198
+ if params[:encrypted_session_key]
199
+ return msg_encryptor.decrypt_and_verify(params[:encrypted_session_key])
186
200
  end
187
- true
201
+ params[:session_key] || session_key_header
188
202
  end
189
203
 
190
- def cookies_need_iframe_fix?
191
- @browser ||= Browser.new(request.user_agent)
192
- @browser.safari? && !request.referrer&.include?('sessionless_launch') && !session[:safari_cookie_fixed] && !params[:platform_redirect_url]
204
+ def session_key_header
205
+ if (match = request.headers['Authorization'].try(:match, /crypted_token=(.+)/))
206
+ msg_encryptor.decrypt_and_verify(match[1])
207
+ elsif (match = request.headers['Authorization'].try(:match, /token=(.+)/))
208
+ match[1]
209
+ end
193
210
  end
194
211
 
195
- def fix_iframe_cookies
196
- if params[:safari_cookie_fix].present?
197
- session[:safari_cookie_fixed] = true
198
- redirect_to params[:return_to]
212
+ # Redirect with the session key intact. In production,
213
+ # handle this by encrypting the session key. That way if the
214
+ # url is logged anywhere, it will all be encrypted data. In dev,
215
+ # just put it in the URL. Putting it in the URL
216
+ # is insecure, but is fine in development.
217
+ # Keeping it in the URL in development means that it plays
218
+ # nicely with webpack-dev-server live reloading (otherwise
219
+ # you get an access error every time it tries to live reload).
220
+
221
+ def redirect_with_session_to(path, id_or_resource = nil, redirect_params = {})
222
+ if Rails.env.development? || Rails.env.test?
223
+ redirect_development_mode(path, id_or_resource, redirect_params)
199
224
  else
200
- render 'coalescing_panda/lti/iframe_cookie_fix', layout: false
225
+ redirect_production_mode(path, id_or_resource, redirect_params)
201
226
  end
202
227
  end
203
228
 
229
+ def redirect_development_mode(path, id_or_resource = nil, redirect_params)
230
+ redirect_to send(path, id_or_resource, {
231
+ session_key: current_session.session_key,
232
+ organization_id: current_lti_account.id
233
+ }.merge(redirect_params))
234
+ end
235
+
236
+ def redirect_production_mode(path, id_or_resource = nil, redirect_params)
237
+ redirect_to send(path, id_or_resource, {
238
+ encrypted_session_key: encrypted_session_key,
239
+ organization_id: current_lti_account.id
240
+ }.merge(redirect_params))
241
+ end
242
+
204
243
  end
205
244
  end
@@ -50,6 +50,9 @@ module CoalescingPanda
50
50
  # https://github.com/MiniProfiler/rack-mini-profiler/issues/327
51
51
  # DON'T ENABLE THIS FOR PRODUCTION!
52
52
  script_src << "'unsafe-eval'"
53
+ elsif CoalescingPanda.lti_options.has_key?(:allow_unsafe_eval) && CoalescingPanda.lti_options[:allow_unsafe_eval] == true
54
+ # For when code is returned from server and injected into dom. Need to have unsafe-eval or it won't work.
55
+ script_src << "'unsafe-eval'"
53
56
  end
54
57
 
55
58
  SecureHeaders::Configuration.default do |config|
@@ -80,6 +83,10 @@ module CoalescingPanda
80
83
  SecureHeaders::Configuration.override(:safari_override) do |config|
81
84
  config.cookies = SecureHeaders::OPT_OUT
82
85
  end
86
+
87
+ SecureHeaders::Configuration.override(:allow_inline_scripts) do |config|
88
+ config.csp[:script_src] << "'unsafe-inline'"
89
+ end
83
90
  end
84
91
 
85
92
  end
@@ -1,3 +1,3 @@
1
1
  module CoalescingPanda
2
- VERSION = '4.7.0'
2
+ VERSION = '5.0.1'
3
3
  end
@@ -173,6 +173,17 @@ ActiveRecord::Schema.define(version: 20160830183155) do
173
173
 
174
174
  add_index "coalescing_panda_oauth_states", ["state_key"], name: "index_coalescing_panda_oauth_states_on_state_key", unique: true
175
175
 
176
+ create_table "coalescing_panda_persistent_sessions", force: :cascade do |t|
177
+ t.string "session_key"
178
+ t.text "data"
179
+ t.integer "coalescing_panda_lti_account_id"
180
+ t.datetime "created_at", null: false
181
+ t.datetime "updated_at", null: false
182
+ end
183
+
184
+ add_index "coalescing_panda_persistent_sessions", ["coalescing_panda_lti_account_id"], name: "index_persistent_session_on_lti_account_id", using: :btree
185
+ add_index "coalescing_panda_persistent_sessions", ["session_key"], name: "index_coalescing_panda_persistent_sessions_on_session_key", unique: true, using: :btree
186
+
176
187
  create_table "coalescing_panda_sections", force: :cascade do |t|
177
188
  t.integer "coalescing_panda_course_id", null: false
178
189
  t.string "name"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coalescing_panda
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.7.0
4
+ version: 5.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Mills
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2020-03-24 00:00:00.000000000 Z
13
+ date: 2020-07-22 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rails
@@ -416,6 +416,7 @@ files:
416
416
  - app/models/coalescing_panda/workers/course_miner.rb
417
417
  - app/models/coalescing_panda/lti_nonce.rb
418
418
  - app/models/coalescing_panda/course.rb
419
+ - app/models/coalescing_panda/persistent_session.rb
419
420
  - app/models/coalescing_panda/lti_account.rb
420
421
  - app/models/coalescing_panda/user.rb
421
422
  - app/models/coalescing_panda/group_category.rb
@@ -463,6 +464,7 @@ files:
463
464
  - db/migrate/20141120153135_create_coalescing_panda_enrollments.rb
464
465
  - db/migrate/20150107205405_create_coalescing_panda_groups.rb
465
466
  - db/migrate/20141119225721_create_coalescing_panda_courses.rb
467
+ - db/migrate/20200528224505_create_coalescing_panda_persistent_session.rb
466
468
  - db/migrate/20141124160857_create_delayed_jobs.rb
467
469
  - db/migrate/20131119165343_create_coalescing_panda_lti_nonces.rb
468
470
  - db/migrate/20150106180131_add_published_to_assignments.rb