coalescing_panda 5.0.10 → 5.1.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: 0d5aa87e2a3916918af65a1e1f277bc43d7f7e84
4
- data.tar.gz: 442e7c12a0bc6e5b9ff459c1a5b7e35f069961c9
3
+ metadata.gz: ca4769293181b5ee47471cc9051ad0b678915f71
4
+ data.tar.gz: 60c8585ff7f5d28057b703a9ae7c5a85ab6dbdb7
5
5
  SHA512:
6
- metadata.gz: 13ce225d0d9f95cf1f56327fa231d8680611ad55b9b3ceab43cbf11da20775b1ccd54a5ffb564b0460c75e2ad238ccd4b1b2e9ebddfd5bda51280b7b320e5fbb
7
- data.tar.gz: dd493d6bf57b29132db15a9a3cd48bcb85b924591c77b39d9c1e2d22a9a486755ea077c412f98034cce860002af797189e7464b461d4d0bfdf7167948b6b2688
6
+ metadata.gz: 6103b51481915a9d2719a87ff81c042a783d0111108e4c270fc3261a61008ab3683beaae3b2a9490450248fe882f4be75776d360219fe2dadb7377cc301f229f
7
+ data.tar.gz: f434c3a048769d55b72fe0ba190c7033ce0429b0acbf3ec0ec1a2ea2bc920262de49ef9336feb5e236d1767237bd79dd4ccf6bf14765c79358faa24fa329e91c
@@ -34,7 +34,7 @@ module CoalescingPanda
34
34
  private
35
35
 
36
36
  def oauth2_protocol
37
- ENV['OAUTH_PROTOCOL'] || 'https'
37
+ ENV['OAUTH_PROTOCOL'] || (Rails.env.development? ? 'http' : 'https')
38
38
  end
39
39
 
40
40
  def retrieve_oauth_state
@@ -1,51 +1,17 @@
1
1
  require 'browser'
2
+ require_relative 'session_replacement'
2
3
 
3
4
  module CoalescingPanda
4
5
  module ControllerHelpers
5
6
  extend ActiveSupport::Concern
6
-
7
- included do
8
- alias_method :rails_session, :session
9
-
10
- helper_method :encrypted_session_key, :current_session_data, :current_session
11
- append_after_action :save_session, if: -> { @current_session && session_changed? }
12
- end
13
-
14
- class_methods do
15
- def use_native_sessions
16
- after_action do
17
- rails_session['persistent_session_key'] = current_session.session_key if @current_session.present?
18
- end
19
- end
20
- end
21
-
22
- def current_session
23
- @current_session ||= (CoalescingPanda::PersistentSession.find_by(session_key: session_key) if session_key)
24
- @current_session ||= (CoalescingPanda::PersistentSession.create_from_launch(params, current_lti_account.id) if current_lti_account.present?)
25
- @current_session
26
- end
7
+ include SessionReplacement
27
8
 
28
9
  def current_lti_account
29
10
  @account ||= (CoalescingPanda::LtiAccount.find_by!(key: organization_key) if organization_key)
30
11
  @account ||= (CoalescingPanda::LtiAccount.find_by(id: organization_id) if organization_id)
31
12
  @account
32
13
  end
33
-
34
- def current_session_data
35
- current_session.data
36
- end
37
-
38
- def encrypted_session_key
39
- msg_encryptor.encrypt_and_sign(current_session.session_key)
40
- end
41
-
42
- def save_session
43
- current_session.try(:save)
44
- end
45
-
46
- def session_changed?
47
- current_session.changed? && current_session.changes[:data].present?
48
- end
14
+ def current_organization; current_lti_account; end
49
15
 
50
16
  def canvas_oauth2(*roles)
51
17
  return if have_session?
@@ -191,14 +157,22 @@ module CoalescingPanda
191
157
  end
192
158
  end
193
159
 
194
- def session_check
195
- logger.warn 'session_check is deprecated. Functionality moved to lti_authorize.'
160
+ def valid_session?
161
+ [
162
+ current_session&.persisted?,
163
+ ].all?
164
+ rescue SessionNonceMismatch
165
+ false
196
166
  end
197
167
 
198
168
  private
199
169
 
200
- def msg_encryptor
201
- @crypt ||= ActiveSupport::MessageEncryptor.new(Rails.application.secrets.secret_key_base[0..31])
170
+ def find_or_create_session(key:)
171
+ if key == :create
172
+ CoalescingPanda::PersistentSession.create_from_launch(params, current_lti_account.id) if current_lti_account.present?
173
+ else
174
+ CoalescingPanda::PersistentSession.find_by(session_key: key)
175
+ end
202
176
  end
203
177
 
204
178
  def organization_key
@@ -209,54 +183,6 @@ module CoalescingPanda
209
183
  params[:organization_id] || (current_session_data[:launch_params][:organization_id] if @current_session)
210
184
  end
211
185
 
212
- def session_key
213
- if params[:encrypted_session_key]
214
- return msg_encryptor.decrypt_and_verify(params[:encrypted_session_key])
215
- end
216
- params[:session_key] || session_key_header || rails_session['persistent_session_key']
217
- end
218
-
219
- def session_key_header
220
- if (match = request.headers['Authorization'].try(:match, /crypted_token=(.+)/))
221
- msg_encryptor.decrypt_and_verify(match[1])
222
- elsif (match = request.headers['Authorization'].try(:match, /token=(.+)/))
223
- match[1]
224
- end
225
- end
226
-
227
- # Redirect with the session key intact. In production,
228
- # handle this by encrypting the session key. That way if the
229
- # url is logged anywhere, it will all be encrypted data. In dev,
230
- # just put it in the URL. Putting it in the URL
231
- # is insecure, but is fine in development.
232
- # Keeping it in the URL in development means that it plays
233
- # nicely with webpack-dev-server live reloading (otherwise
234
- # you get an access error every time it tries to live reload).
235
-
236
- def redirect_with_session_to(path, id_or_resource = nil, redirect_params = {})
237
- if Rails.env.development? || Rails.env.test?
238
- redirect_development_mode(path, id_or_resource, redirect_params)
239
- else
240
- redirect_production_mode(path, id_or_resource, redirect_params)
241
- end
242
- end
243
-
244
- def redirect_development_mode(path, id_or_resource = nil, redirect_params)
245
- redirect_to send(path, id_or_resource, {
246
- session_key: current_session.session_key,
247
- organization_id: current_lti_account.id
248
- }.merge(redirect_params))
249
- end
250
-
251
- def redirect_production_mode(path, id_or_resource = nil, redirect_params)
252
- redirect_to send(path, id_or_resource, {
253
- encrypted_session_key: encrypted_session_key,
254
- organization_id: current_lti_account.id
255
- }.merge(redirect_params))
256
- end
257
-
258
- private
259
-
260
186
  # This is necessitated by a bug in Rails Engines where it isn't resolving the URL correctly
261
187
  # when using coalescing_panda.xyz_url (The Engine Prefix is not included)
262
188
  # I believe https://github.com/rails/rails/issues/34452 is the same issue
@@ -48,11 +48,6 @@ module CoalescingPanda
48
48
  end
49
49
  end
50
50
 
51
- if CoalescingPanda.lti_options.has_key?(:allow_unsafe_eval) && CoalescingPanda.lti_options[:allow_unsafe_eval] == true
52
- # For when code is returned from server and injected into dom. Need to have unsafe-eval or it won't work.
53
- csp_entry(:script_src, "'unsafe-eval'")
54
- end
55
-
56
51
  # Detect and permit Sentry
57
52
  if defined?(Raven) && Raven.configuration.server.present?
58
53
  csp_entry(:connect_src, Raven.configuration.server)
@@ -0,0 +1,185 @@
1
+ module CoalescingPanda
2
+ class SessionNonceMismatch < StandardError; end
3
+
4
+ module SessionReplacement
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ helper_method :link_nonce, :current_session, :current_session_data
9
+ helper_method :link_with_session_to, :url_with_session, :session_url_for
10
+
11
+ prepend_around_action :monkeypatch_flash
12
+ prepend_around_action :auto_save_session
13
+ end
14
+
15
+ class_methods do
16
+ def link_nonce_type(value = :not_given)
17
+ if value == :not_given
18
+ @link_nonce_type || superclass.try(:link_nonce_type) || :nonce
19
+ else
20
+ @link_nonce_type = value
21
+ end
22
+ end
23
+ end
24
+
25
+ def save_session
26
+ current_session.try(:save)
27
+ end
28
+
29
+ def current_session
30
+ return @current_session if @current_session.present?
31
+
32
+ if params[:session_token]
33
+ payload = JSON.parse(session_cryptor.decrypt_and_verify(params[:session_token])).with_indifferent_access
34
+ matched_session = find_or_create_session(key: payload[:session_key])
35
+
36
+ if matched_session.present?
37
+ if payload[:token_type] == 'nonce' && matched_session.data[:link_nonce] == payload[:nonce]
38
+ @current_session = matched_session
39
+ @current_session.data[:link_nonce] = nil
40
+ elsif payload[:token_type] == 'fixed_ip' && matched_session.data[:remote_ip] == request.remote_ip &&
41
+ DateTime.parse(matched_session.data[:last_ip_token_requested]) > 15.minutes.ago
42
+ @current_session = matched_session
43
+ end
44
+ end
45
+ raise SessionNonceMismatch, "Session Not Found" unless @current_session.present?
46
+ elsif (session_key = params[:session_key] || session_key_header || flash[:session_key] || session[:session_key]).present?
47
+ @current_session = find_or_create_session(key: session_key)
48
+ end
49
+
50
+ @current_session ||= find_or_create_session(key: :create)
51
+
52
+ @current_session
53
+ end
54
+
55
+ def current_session_data
56
+ current_session.data
57
+ end
58
+
59
+ def session_changed?
60
+ current_session.changed? && current_session.changes[:data].present?
61
+ end
62
+
63
+ def forbid_access_if_lacking_session
64
+ render plain: 'You should do an LTI Tool Launch.', status: :unauthorized unless valid_session?
65
+ end
66
+
67
+ def verify_authenticity_token
68
+ # No need to check CSRF when no cookies were sent. This fixes CSRF failures in Browsers
69
+ # that restrict Cookie setting within an IFrame.
70
+ return unless request.cookies.keys.length > 0
71
+ super
72
+ end
73
+
74
+ # Redirect with the session key intact. In production,
75
+ # handle this by adding a one-time use encrypted token to the URL.
76
+ # Keeping it in the URL in development means that it plays
77
+ # nicely with webpack-dev-server live reloading (otherwise
78
+ # you get an access error everytime it tries to live reload).
79
+
80
+ def redirect_with_session_to(*args)
81
+ redirect_to url_with_session(*args)
82
+ end
83
+
84
+ def link_with_session_to(*args)
85
+ helpers.link_to url_with_session(*args)
86
+ end
87
+
88
+ def session_url_for(*args)
89
+ url_for(build_session_url_params(*args))
90
+ end
91
+
92
+ def url_with_session(location, *args, route_context: self, **kwargs)
93
+ route_context.send(location, *build_session_url_params(*args, **kwargs))
94
+ end
95
+
96
+ def link_nonce(type: link_nonce_type)
97
+ type = instance_exec(&type) if type.is_a?(Proc)
98
+ type = type.to_s
99
+
100
+ @cached_link_nonces ||= {}
101
+ @cached_link_nonces[type] ||= begin
102
+ payload = {
103
+ token_type: type,
104
+ session_key: current_session.session_key,
105
+ organization_id: current_organization.id,
106
+ }
107
+
108
+ if type == 'nonce'
109
+ current_session_data[:link_nonce] = SecureRandom.hex
110
+ payload.merge!(nonce: current_session_data[:link_nonce])
111
+ elsif type == 'fixed_ip'
112
+ current_session_data[:remote_ip] ||= request.remote_ip
113
+ current_session_data[:last_ip_token_requested] = DateTime.now.iso8601
114
+ else
115
+ raise StandardError, "Unsupported link_nonce_type: '#{type}'"
116
+ end
117
+
118
+ session_cryptor.encrypt_and_sign(payload.to_json)
119
+ end
120
+ end
121
+
122
+ def link_nonce_type
123
+ self.class.link_nonce_type
124
+ end
125
+
126
+ private
127
+
128
+ def session_cryptor
129
+ secret_key_base = Rails.application.try(:secret_key_base) || Rails.application.secrets.secret_key_base
130
+ @session_cryptor ||= ActiveSupport::MessageEncryptor.new(secret_key_base[0..31])
131
+ end
132
+
133
+ def session_key_header
134
+ if match = request.headers['Authorization'].try(:match, /token=(.+)/)
135
+ match[1]
136
+ end
137
+ end
138
+
139
+ def build_session_url_params(*args, nonce_type: link_nonce_type, **kwargs)
140
+ if args[-1].is_a?(Hash)
141
+ args[-1] = args[-1].dup
142
+ else
143
+ args.push({})
144
+ end
145
+
146
+ if Rails.env.development?
147
+ args[-1].merge!(
148
+ session_key: current_session.session_key,
149
+ organization_id: current_organization.id,
150
+ )
151
+ else
152
+ args[-1].merge!(
153
+ session_token: link_nonce(type: nonce_type),
154
+ organization_id: current_organization.id,
155
+ )
156
+ end
157
+
158
+ args[-1].merge!(kwargs)
159
+ args
160
+ end
161
+
162
+ def auto_save_session
163
+ yield if block_given?
164
+ save_session if @current_session && session_changed?
165
+ end
166
+
167
+ def monkeypatch_flash
168
+ if valid_session? && (value = current_session_data['flashes']).present?
169
+ flashes = value["flashes"]
170
+ if discard = value["discard"]
171
+ flashes.except!(*discard)
172
+ end
173
+ flash.replace(flashes)
174
+ flash.discard()
175
+ end
176
+
177
+ yield
178
+
179
+ if @current_session.present?
180
+ current_session_data['flashes'] = flash.to_session_value
181
+ flash.discard()
182
+ end
183
+ end
184
+ end
185
+ end
@@ -1,3 +1,3 @@
1
1
  module CoalescingPanda
2
- VERSION = '5.0.10'
2
+ VERSION = '5.1.0'
3
3
  end
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: 5.0.10
4
+ version: 5.1.0
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-09-10 00:00:00.000000000 Z
13
+ date: 2020-09-14 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rails
@@ -490,6 +490,7 @@ files:
490
490
  - lib/coalescing_panda/misc_helper.rb
491
491
  - lib/coalescing_panda/route_helpers.rb
492
492
  - lib/coalescing_panda/secure_headers.rb
493
+ - lib/coalescing_panda/session_replacement.rb
493
494
  - lib/coalescing_panda/version.rb
494
495
  - lib/tasks/coalescing_panda_tasks.rake
495
496
  - spec/controllers/coalescing_panda/canvas_batches_controller_spec.rb