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 +4 -4
- data/app/controllers/coalescing_panda/oauth2_controller.rb +1 -1
- data/lib/coalescing_panda/controller_helpers.rb +15 -89
- data/lib/coalescing_panda/secure_headers.rb +0 -5
- data/lib/coalescing_panda/session_replacement.rb +185 -0
- data/lib/coalescing_panda/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca4769293181b5ee47471cc9051ad0b678915f71
|
4
|
+
data.tar.gz: 60c8585ff7f5d28057b703a9ae7c5a85ab6dbdb7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6103b51481915a9d2719a87ff81c042a783d0111108e4c270fc3261a61008ab3683beaae3b2a9490450248fe882f4be75776d360219fe2dadb7377cc301f229f
|
7
|
+
data.tar.gz: f434c3a048769d55b72fe0ba190c7033ce0429b0acbf3ec0ec1a2ea2bc920262de49ef9336feb5e236d1767237bd79dd4ccf6bf14765c79358faa24fa329e91c
|
@@ -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
|
195
|
-
|
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
|
201
|
-
|
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
|
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
|
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-
|
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
|