coalescing_panda 5.0.10 → 5.1.0

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: 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