coalescing_panda 5.0.7 → 5.1.2

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
- SHA256:
3
- metadata.gz: 671ec79eae7cb32383d46dd2fd788864463b8e3258a501e0cd16d6b98eb8b384
4
- data.tar.gz: 6d7d5176bbba7a011279924cb17b3cc953d73bcf1fc516bdaf39df6b7db03f23
2
+ SHA1:
3
+ metadata.gz: db38b8cdb32e3f3d41eab15a3df4542cb9feeb9b
4
+ data.tar.gz: 4cb1f0ec31e628852a3cfe2100b2ca31a91f94cd
5
5
  SHA512:
6
- metadata.gz: 2bd75383b5aa806226be52acebd1efa38b02103bf38d71784c4c7933f47dbbdf6e3dc919155e99783a08b760ab3a53caa51ef38213e9eaa8daf6d158778c181a
7
- data.tar.gz: 425b73b527dd3598d1afbec15e9e67a306209d337f4cb944bf34dfa075b4f080f6c2d6306566b4764b8c5a87f0a262389d306c8d418a6dba802ea09f067428d0
6
+ metadata.gz: d05f38b6ab1f690c9b581c59da23814f6b2b505be56bf2ab057b3099dd15497d98de974ce9f43f779e6b59e863e825fb690d146121915da348484af391798518
7
+ data.tar.gz: 3663adb9a48a839fed284a7132083335578074ce210b382cbac8e6cb4a0a98aa59d0033ad4cc6b8116205143b13a787d5ed56fa65ca7eef0f7e38b9ec23e551c
@@ -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,4 +1,4 @@
1
1
  - if current_batch.present?
2
- - path = CoalescingPanda::Engine.routes.url_helpers.canvas_batch_path(current_batch)
3
- - clear_path = CoalescingPanda::Engine.routes.url_helpers.clear_batch_session_path
4
- #batch-progress{data: {batch: current_batch.try(:to_json), url: path, clear_path: clear_path} }
2
+ - path = CoalescingPanda::Engine.routes.url_helpers.canvas_batch_path(current_batch) + "?encrypted_session_key=#{encrypted_session_key}"
3
+ - clear_path = CoalescingPanda::Engine.routes.url_helpers.clear_batch_session_path + "?encrypted_session_key=#{encrypted_session_key}"
4
+ #batch-progress{data: {batch: current_batch.try(:to_json), url: path, clear_path: clear_path} }
@@ -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?
@@ -81,9 +47,7 @@ module CoalescingPanda
81
47
  client = Bearcat::Client.new(prefix: uri.prefix)
82
48
  state = SecureRandom.hex(32)
83
49
  OauthState.create! state_key: state, data: { key: params['oauth_consumer_key'], user_id: user_id, api_domain: uri.api_domain }
84
- redirect_path = coalescing_panda.oauth2_redirect_path
85
- redirect_url = [coalescing_panda_url, redirect_path.sub(/^\/lti/, '')].join
86
- @canvas_url = client.auth_redirect_url(client_id, redirect_url, { state: state })
50
+ @canvas_url = client.auth_redirect_url(client_id, resolve_coalescing_panda_url(:oauth2_redirect_url), { state: state })
87
51
 
88
52
  #delete the added params so the original oauth sig still works
89
53
  @lti_params = params.to_hash
@@ -94,7 +58,7 @@ module CoalescingPanda
94
58
 
95
59
  def refresh_token(uri, api_auth)
96
60
  refresh_client = Bearcat::Client.new(prefix: uri.prefix)
97
- refresh_body = refresh_client.retrieve_token(@lti_account.oauth2_client_id, coalescing_panda.oauth2_redirect_url,
61
+ refresh_body = refresh_client.retrieve_token(@lti_account.oauth2_client_id, resolve_coalescing_panda_url(:oauth2_redirect_url),
98
62
  @lti_account.oauth2_client_key, api_auth.refresh_token, 'refresh_token')
99
63
  api_auth.update({ api_token: refresh_body['access_token'], expires_at: (Time.now + refresh_body['expires_in']) })
100
64
  end
@@ -193,14 +157,21 @@ module CoalescingPanda
193
157
  end
194
158
  end
195
159
 
196
- def session_check
197
- logger.warn 'session_check is deprecated. Functionality moved to lti_authorize.'
160
+ def valid_session?
161
+ return false unless current_session(create_missing: false)&.persisted?
162
+ true
163
+ rescue SessionNonceMismatch
164
+ false
198
165
  end
199
166
 
200
167
  private
201
168
 
202
- def msg_encryptor
203
- @crypt ||= ActiveSupport::MessageEncryptor.new(Rails.application.secrets.secret_key_base[0..31])
169
+ def find_or_create_session(key:)
170
+ if key == :create
171
+ CoalescingPanda::PersistentSession.create_from_launch(params, current_lti_account.id) if current_lti_account.present?
172
+ else
173
+ CoalescingPanda::PersistentSession.find_by(session_key: key)
174
+ end
204
175
  end
205
176
 
206
177
  def organization_key
@@ -211,51 +182,16 @@ module CoalescingPanda
211
182
  params[:organization_id] || (current_session_data[:launch_params][:organization_id] if @current_session)
212
183
  end
213
184
 
214
- def session_key
215
- if params[:encrypted_session_key]
216
- return msg_encryptor.decrypt_and_verify(params[:encrypted_session_key])
217
- end
218
- params[:session_key] || session_key_header || rails_session['persistent_session_key']
185
+ # This is necessitated by a bug in Rails Engines where it isn't resolving the URL correctly
186
+ # when using coalescing_panda.xyz_url (The Engine Prefix is not included)
187
+ # I believe https://github.com/rails/rails/issues/34452 is the same issue
188
+ def resolve_coalescing_panda_url(key)
189
+ key = key.to_s[0...-4] if key.to_s.ends_with?('_url')
190
+ resolved_path = coalescing_panda.send(:"#{key}_path")
191
+ cpurl = coalescing_panda_url
192
+ cppath = URI.parse(cpurl).path
193
+ resolved_path = cppath + resolved_path unless resolved_path.starts_with?(cppath)
194
+ URI.join(cpurl, resolved_path)
219
195
  end
220
-
221
- def session_key_header
222
- if (match = request.headers['Authorization'].try(:match, /crypted_token=(.+)/))
223
- msg_encryptor.decrypt_and_verify(match[1])
224
- elsif (match = request.headers['Authorization'].try(:match, /token=(.+)/))
225
- match[1]
226
- end
227
- end
228
-
229
- # Redirect with the session key intact. In production,
230
- # handle this by encrypting the session key. That way if the
231
- # url is logged anywhere, it will all be encrypted data. In dev,
232
- # just put it in the URL. Putting it in the URL
233
- # is insecure, but is fine in development.
234
- # Keeping it in the URL in development means that it plays
235
- # nicely with webpack-dev-server live reloading (otherwise
236
- # you get an access error every time it tries to live reload).
237
-
238
- def redirect_with_session_to(path, id_or_resource = nil, redirect_params = {})
239
- if Rails.env.development? || Rails.env.test?
240
- redirect_development_mode(path, id_or_resource, redirect_params)
241
- else
242
- redirect_production_mode(path, id_or_resource, redirect_params)
243
- end
244
- end
245
-
246
- def redirect_development_mode(path, id_or_resource = nil, redirect_params)
247
- redirect_to send(path, id_or_resource, {
248
- session_key: current_session.session_key,
249
- organization_id: current_lti_account.id
250
- }.merge(redirect_params))
251
- end
252
-
253
- def redirect_production_mode(path, id_or_resource = nil, redirect_params)
254
- redirect_to send(path, id_or_resource, {
255
- encrypted_session_key: encrypted_session_key,
256
- organization_id: current_lti_account.id
257
- }.merge(redirect_params))
258
- end
259
-
260
196
  end
261
197
  end
@@ -4,9 +4,9 @@ module CoalescingPanda
4
4
 
5
5
  def self.to_boolean(v)
6
6
  if Rails.version < '5.0'
7
- ActiveRecord::Type::Boolean.new.type_cast_from_user("0")
7
+ ActiveRecord::Type::Boolean.new.type_cast_from_user(v)
8
8
  else
9
- ActiveRecord::Type::Boolean.new.deserialize('0')
9
+ ActiveRecord::Type::Boolean.new.deserialize(v)
10
10
  end
11
11
  end
12
12
  end
@@ -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(create_missing: true)
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) if create_missing
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.7'
2
+ VERSION = '5.1.2'
3
3
  end
@@ -11,7 +11,7 @@ describe CoalescingPanda::Oauth2Controller, :type => :controller do
11
11
  Bearcat::Client.any_instance.stub(retrieve_token: { 'access_token' => 'token', 'refresh_token' => 'token', 'expires_in' => 3600 })
12
12
  session[:state] = 'test'
13
13
  CoalescingPanda::OauthState.create!(state_key: session[:state], data: { key: account.key, user_id: user.id, api_domain: 'foo.com' })
14
- get :redirect, {user_id: user.id, api_domain: 'foo.com', code: 'bar', key: account.key, state: 'test'}
14
+ get :redirect, params: {user_id: user.id, api_domain: 'foo.com', code: 'bar', key: account.key, state: 'test'}
15
15
  auth = CoalescingPanda::CanvasApiAuth.find_by_user_id_and_api_domain(user.id, 'foo.com')
16
16
  auth.should_not == nil
17
17
  expect(auth.api_token).to eql 'token'
@@ -20,7 +20,7 @@ describe CoalescingPanda::Oauth2Controller, :type => :controller do
20
20
  end
21
21
 
22
22
  it "doesn't create a token in the db" do
23
- get :redirect, {error: 'your face'}
23
+ get :redirect, params: {error: 'your face'}
24
24
  CoalescingPanda::CanvasApiAuth.all.count.should == 0
25
25
  end
26
26
  end
@@ -1,10 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe CoalescingPanda::CanvasApiAuth do
3
+ describe CoalescingPanda::CanvasApiAuth, type: :model do
4
4
 
5
5
  it { should validate_uniqueness_of(:user_id).scoped_to(:api_domain)}
6
6
  it { should validate_presence_of(:user_id)}
7
- it {should validate_presence_of(:api_domain)}
7
+ it { should validate_presence_of(:api_domain)}
8
8
 
9
9
  describe '#expired?' do
10
10
  let(:auth) { FactoryGirl.create :canvas_api_auth }
@@ -24,6 +24,13 @@ SimpleCov.start
24
24
 
25
25
  ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)
26
26
 
27
+ Shoulda::Matchers.configure do |config|
28
+ config.integrate do |with|
29
+ with.test_framework :rspec
30
+ with.library :rails
31
+ end
32
+ end
33
+
27
34
  # This file was generated by the `rails generate rspec:install` command. Conventionally, all
28
35
  # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
29
36
  # The generated `.rspec` file contains `--require spec_helper` which will cause this
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.7
4
+ version: 5.1.2
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-08-26 00:00:00.000000000 Z
13
+ date: 2020-09-16 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
@@ -577,7 +578,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
577
578
  - !ruby/object:Gem::Version
578
579
  version: '0'
579
580
  requirements: []
580
- rubygems_version: 3.1.2
581
+ rubyforge_project:
582
+ rubygems_version: 2.6.14.4
581
583
  signing_key:
582
584
  specification_version: 4
583
585
  summary: Canvas LTI and OAUTH2 mountable engine