coalescing_panda 4.7.0 → 5.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 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