coalescing_panda 5.1.12 → 5.2.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
  SHA256:
3
- metadata.gz: 45ffb1d5dba84eb776180589d036732efebe48d87472dfd8fbf620b7f38c1747
4
- data.tar.gz: 3f558eb4cb87853533369fa838bd92546060f5f6235c17655ee5c1908314d7f1
3
+ metadata.gz: 11cc21d4b7941edd42fc5a17f198a55766a46fed5ea37281389f91f71703636e
4
+ data.tar.gz: e4c4173587ad7feabf2478c98816b958cf153360da52b034656caf2e6a2a3d33
5
5
  SHA512:
6
- metadata.gz: ca3c23e381fedb64619951fd7ca1687c3f4aa77ba3395f59e62ebea73a6a5b8703f912afece5d5069b2c94de1daed3e2d998d3e3ffcec28f0ed448f8d01556e1
7
- data.tar.gz: 0d04904eec7b603682610cd92d9b482b11e9295f66e319c963d27182edca19db81c2fcdfc19cc7baa732fa3f09a7b05059e50705832f094bececac857c29164b
6
+ metadata.gz: 8bd652d61d8e4968e1b8289c44d8be96c6602c5f60c8e2ef0cc17789d57b67a1b8dfb88fd746c2655d99b0c8c954225853a7883531f4d565d759b2836eea4f5f
7
+ data.tar.gz: 23178183610e5449d6bf5c41d90f02b83f6f71b5f5c2a4c4ff2d7f2028c9063d5873795dc2f7015c86857a401bf1da4c5de38c6d5461a8993b2ac2f443d04639
@@ -15,10 +15,12 @@ module CoalescingPanda
15
15
  client_key = lti_account.oauth2_client_key
16
16
  user_id = @oauth_state.data[:user_id]
17
17
  api_domain = @oauth_state.data[:api_domain]
18
+ prefix = @oauth_state.data[:api_url]
18
19
  @oauth_state.destroy
19
- prefix = [oauth2_protocol, '://', api_domain].join
20
+
20
21
  Rails.logger.info "Creating Bearcat client for auth token retrieval pointed to: #{prefix}"
21
22
  client = Bearcat::Client.new(prefix: prefix)
23
+
22
24
  token_body = client.retrieve_token(client_id, coalescing_panda.oauth2_redirect_url, client_key, params['code'])
23
25
  auth = CanvasApiAuth.where('user_id = ? and api_domain = ?', user_id, api_domain).first_or_initialize
24
26
  auth.api_token = token_body['access_token']
@@ -30,20 +32,10 @@ module CoalescingPanda
30
32
  end
31
33
  end
32
34
 
33
-
34
35
  private
35
36
 
36
- def oauth2_protocol
37
- ENV['OAUTH_PROTOCOL'] || (Rails.env.development? ? 'http' : 'https')
38
- end
39
-
40
37
  def retrieve_oauth_state
41
38
  @oauth_state ||= params[:state].present? && OauthState.find_by(state_key: params[:state])
42
39
  end
43
-
44
- def valid_state_token
45
- return false unless params['state'].present? && session['state'].present?
46
- params['state'] == session['state']
47
- end
48
40
  end
49
41
  end
@@ -3,7 +3,9 @@
3
3
  %h2 Canvas Authentication Required
4
4
  %button.btn-canvas#oauth_btn Authenticate
5
5
 
6
- %form{id: 'lti_form', action: "#{request.original_url}", method: 'post'}
6
+ %form{id: 'lti_form', action: "#{request.original_url}", method: request.method}
7
+ - unless @lti_params.include?(:session_token) || @lti_params.include?(:session_key)
8
+ %input{:type =>"hidden", :value=>link_nonce, :name=>"session_token"}
7
9
  - @lti_params.each do |k,v|
8
10
  %input{:type =>"hidden", :value=>"#{v}", :name=>"#{k}"}
9
11
 
@@ -1,24 +1,26 @@
1
- class CoalescingPanda::BearcatUri
2
- attr_accessor :uri
1
+ module CoalescingPanda
2
+ class BearcatUri
3
+ attr_accessor :uri
3
4
 
4
- def initialize(uri)
5
- Rails.logger.info "Parsing Bearcat URI: #{uri}"
6
- @uri = URI.parse(uri)
7
- end
5
+ def initialize(uri)
6
+ Rails.logger.info "Parsing Bearcat URI: #{uri}"
7
+ @uri = URI.parse(uri)
8
+ end
8
9
 
9
- def api_domain
10
- if Rails.env.test? or Rails.env.development?
11
- uri.port.present? ? URI.encode("#{uri.host}:#{uri.port.to_s}") : uri.host
12
- else
13
- uri.host
10
+ def api_domain
11
+ if Rails.env.test? or Rails.env.development?
12
+ uri.port.present? ? URI.encode("#{uri.host}:#{uri.port.to_s}") : uri.host
13
+ else
14
+ uri.host
15
+ end
14
16
  end
15
- end
16
17
 
17
- def scheme
18
- [uri.scheme, '://'].join
19
- end
18
+ def scheme
19
+ [uri.scheme, '://'].join
20
+ end
20
21
 
21
- def prefix
22
- [scheme, api_domain].join
22
+ def prefix
23
+ [scheme, api_domain].join
24
+ end
23
25
  end
24
- end
26
+ end
@@ -1,5 +1,6 @@
1
1
  require 'browser'
2
2
  require_relative 'session_replacement'
3
+ require_relative 'bearcat_uri'
3
4
 
4
5
  module CoalescingPanda
5
6
  module ControllerHelpers
@@ -14,96 +15,77 @@ module CoalescingPanda
14
15
  def current_organization; current_lti_account; end
15
16
 
16
17
  def canvas_oauth2(*roles)
17
- return if have_session?
18
- if lti_authorize!(*roles)
19
- user_id = params['user_id']
20
- launch_presentation_return_url = @lti_account.settings[:launch_presentation_return_url] || params['launch_presentation_return_url']
21
- launch_presentation_return_url = [CoalescingPanda::BearcatUri.new(request.env["HTTP_REFERER"]).prefix, launch_presentation_return_url].join unless launch_presentation_return_url.include?('http')
22
- uri = CoalescingPanda::BearcatUri.new(launch_presentation_return_url)
23
- set_session(launch_presentation_return_url)
24
-
25
- api_auth = CanvasApiAuth.find_by('user_id = ? and api_domain = ?', user_id, uri.api_domain)
26
- if api_auth
27
- begin
28
- refresh_token(uri, api_auth) if api_auth.expired?
29
- @client = Bearcat::Client.new(token: api_auth.api_token, prefix: uri.prefix)
30
- @client.user_profile 'self'
31
- rescue Footrest::HttpError::BadRequest, Footrest::HttpError::Unauthorized
32
- # If we can't retrieve our own user profile, or the token refresh fails, something is awry on the canvas end
33
- # and we'll need to go through the oauth flow again
34
- render_oauth2_page uri, user_id
35
- end
36
- else
37
- render_oauth2_page uri, user_id
38
- end
18
+ unless valid_session?
19
+ lti_authorize!(*roles)
20
+
21
+ current_session_data['user_id'] = params['user_id']
22
+ current_session_data['lis_person_sourcedid'] = params['lis_person_sourcedid']
23
+ current_session_data['oauth_consumer_key'] = params['oauth_consumer_key']
24
+ current_session_data['custom_canvas_account_id'] = params['custom_canvas_account_id']
39
25
  end
26
+
27
+ @lti_account = LtiAccount.find_by_key(current_session_data['oauth_consumer_key']) if current_session_data['oauth_consumer_key']
28
+
29
+ require_user_api_client
40
30
  end
41
31
 
42
- def render_oauth2_page(uri, user_id)
43
- @lti_account = params['oauth_consumer_key'] && LtiAccount.find_by_key(params['oauth_consumer_key'])
44
- return unless @lti_account
32
+ def render_oauth2_page
33
+ client_id = current_lti_account.oauth2_client_id
34
+ client = Bearcat::Client.new(prefix: canvas_api_uri)
45
35
 
46
- client_id = @lti_account.oauth2_client_id
47
- client = Bearcat::Client.new(prefix: uri.prefix)
48
36
  state = SecureRandom.hex(32)
49
- OauthState.create! state_key: state, data: { key: params['oauth_consumer_key'], user_id: user_id, api_domain: uri.api_domain }
37
+ OauthState.create! state_key: state, data: {
38
+ key: current_session_data.dig(:launch_params, :oauth_consumer_key),
39
+ user_id: current_session_data.dig(:launch_params, :user_id),
40
+ api_domain: URI.parse(canvas_api_uri).host,
41
+ api_url: canvas_api_uri,
42
+ }
43
+
50
44
  @canvas_url = client.auth_redirect_url(client_id, resolve_coalescing_panda_url(:oauth2_redirect_url), { state: state })
51
45
 
52
- #delete the added params so the original oauth sig still works
53
46
  @lti_params = params.to_unsafe_h
54
47
  @lti_params.delete('action')
55
48
  @lti_params.delete('controller')
49
+
56
50
  render 'coalescing_panda/oauth2/oauth2', layout: 'coalescing_panda/application'
57
51
  end
58
52
 
59
- def refresh_token(uri, api_auth)
60
- refresh_client = Bearcat::Client.new(prefix: uri.prefix)
61
- refresh_body = refresh_client.retrieve_token(@lti_account.oauth2_client_id, resolve_coalescing_panda_url(:oauth2_redirect_url),
62
- @lti_account.oauth2_client_key, api_auth.refresh_token, 'refresh_token')
53
+ def refresh_token(api_auth)
54
+ refresh_client = Bearcat::Client.new(prefix: canvas_api_uri)
55
+ refresh_body = refresh_client.retrieve_token(
56
+ current_lti_account.oauth2_client_id,
57
+ resolve_coalescing_panda_url(:oauth2_redirect_url),
58
+ current_lti_account.oauth2_client_key,
59
+ api_auth.refresh_token,
60
+ 'refresh_token'
61
+ )
63
62
  api_auth.update({ api_token: refresh_body['access_token'], expires_at: (Time.now + refresh_body['expires_in']) })
64
63
  end
65
64
 
66
- def check_refresh_token
67
- return unless current_session_data['uri'] && current_session_data['user_id'] && current_session_data['oauth_consumer_key']
68
- uri = CoalescingPanda::BearcatUri.new(current_session_data['uri'])
69
- api_auth = CanvasApiAuth.find_by(user_id: current_session_data['user_id'], api_domain: uri.api_domain)
70
- @lti_account = LtiAccount.find_by(key: current_session_data['oauth_consumer_key'])
71
- return if @lti_account.nil? || api_auth.nil? # Not all tools use oauth
72
-
73
- refresh_token(uri, api_auth) if api_auth.expired?
74
- rescue Footrest::HttpError::BadRequest
75
- render_oauth2_page uri, current_session_data['user_id']
76
- end
77
-
78
- def set_session(launch_presentation_return_url)
79
- current_session_data['user_id'] = params['user_id']
80
- current_session_data['uri'] = launch_presentation_return_url
81
- current_session_data['lis_person_sourcedid'] = params['lis_person_sourcedid']
82
- current_session_data['oauth_consumer_key'] = params['oauth_consumer_key']
83
- current_session_data['custom_canvas_account_id'] = params['custom_canvas_account_id']
84
- end
85
-
86
- def have_session?
87
- if params['tool_consumer_instance_guid'] && current_session_data['user_id'] != params['user_id']
88
- reset_session
89
- logger.info("resetting session params")
90
- current_session_data['user_id'] = params['user_id']
65
+ def require_user_api_client
66
+ if user_api_client.nil?
67
+ render_oauth2_page
91
68
  end
69
+ end
92
70
 
93
- if (current_session_data['user_id'] && current_session_data['uri'])
94
- uri = CoalescingPanda::BearcatUri.new(current_session_data['uri'])
95
- api_auth = CanvasApiAuth.find_by('user_id = ? and api_domain = ?', current_session_data['user_id'], uri.api_domain)
96
- if api_auth && !api_auth.expired?
97
- @client = Bearcat::Client.new(token: api_auth.api_token, prefix: uri.prefix)
98
- @client.user_profile 'self'
71
+ def user_api_client
72
+ @user_api_client ||= begin
73
+ user_id = current_session_data.dig(:launch_params, :user_id)
74
+ puri = URI.parse(canvas_api_uri)
75
+ api_auth = CanvasApiAuth.find_by('user_id = ? and api_domain = ?', user_id, puri.host)
76
+ if api_auth
77
+ begin
78
+ refresh_token(api_auth) if api_auth.expired?
79
+ client = Bearcat::Client.new(token: api_auth.api_token, prefix: canvas_api_uri)
80
+ client.user_profile('self')
81
+ client
82
+ rescue Footrest::HttpError::BadRequest, Footrest::HttpError::Unauthorized
83
+ # If we can't retrieve our own user profile, or the token refresh fails, something is awry on the canvas end
84
+ # and we'll need to go through the oauth flow again
85
+ nil
86
+ end
99
87
  end
100
88
  end
101
-
102
- @lti_account = LtiAccount.find_by_key(current_session_data['oauth_consumer_key']) if current_session_data['oauth_consumer_key']
103
-
104
- !!@client
105
- rescue Footrest::HttpError::Unauthorized
106
- false
107
89
  end
108
90
 
109
91
  def validate_launch!
@@ -111,23 +93,43 @@ module CoalescingPanda
111
93
  end
112
94
 
113
95
  def lti_authorize!(*roles)
96
+ if valid_session? # This means that we are returning from an OAuth dance.
97
+ # Set the params as they were at launch to avoid any bait-and-switch attack vulnerabilities in the App's launch controller
98
+ params.merge!(current_session_data[:launch_params])
99
+ return true
100
+ end
101
+
114
102
  authorized = false
115
103
  if (@lti_account = params['oauth_consumer_key'] && LtiAccount.find_by_key(params['oauth_consumer_key']))
116
104
  sanitized_params = sanitize_params
117
105
  @tp = IMS::LTI::ToolProvider.new(@lti_account.key, @lti_account.secret, sanitized_params)
118
106
  authorized = @tp.valid_request?(request)
119
107
  end
108
+
120
109
  logger.info 'not authorized on tp valid request' unless authorized
121
- authorized = authorized && (roles.count == 0 || (roles & lti_roles).count > 0)
122
- logger.info 'not authorized on roles' unless authorized
123
- authorized = authorized && @lti_account.validate_nonce(params['oauth_nonce'], DateTime.strptime(params['oauth_timestamp'], '%s'))
124
- logger.info 'not authorized on nonce' unless authorized
110
+
111
+ if authorized && !(authorized = (roles.count == 0 || (roles & lti_roles).count > 0))
112
+ logger.info 'not authorized on roles'
113
+ end
114
+
115
+ if authorized && !(authorized = @lti_account.validate_nonce(params['oauth_nonce'], DateTime.strptime(params['oauth_timestamp'], '%s')))
116
+ logger.info 'not authorized on nonce'
117
+ end
118
+
125
119
  render :text => 'Invalid Credentials, please contact your Administrator.', :status => :unauthorized unless authorized
120
+
126
121
  # create session on first launch
127
122
  current_session
128
123
  authorized
129
124
  end
130
125
 
126
+ def valid_session?
127
+ return false unless current_session(create_missing: false)&.persisted?
128
+ true
129
+ rescue SessionNonceMismatch
130
+ false
131
+ end
132
+
131
133
  # code for method taken from panda_pal v 4.0.8
132
134
  # used for safari workaround
133
135
  def sanitize_params
@@ -161,11 +163,27 @@ module CoalescingPanda
161
163
  end
162
164
  end
163
165
 
164
- def valid_session?
165
- return false unless current_session(create_missing: false)&.persisted?
166
- true
167
- rescue SessionNonceMismatch
168
- false
166
+ def canvas_api_uri
167
+ @canvas_api_uri ||= begin
168
+ launch_params = current_session_data[:launch_params] || {}
169
+ uri = 'canvas.instructure.com'
170
+ if launch_params[:custom_canvas_api_domain].present?
171
+ uri = launch_params[:custom_canvas_api_domain]
172
+ else
173
+ uri = current_lti_account.settings[:launch_presentation_return_url] || launch_params[:launch_presentation_return_url]
174
+ unless uri.include?('://')
175
+ referrer = URI.parse(request.env["HTTP_REFERER"])
176
+ referrer.path = ''
177
+ uri = [referrer.to_s, uri].join
178
+ end
179
+ end
180
+
181
+ uri = ((Rails.env.test? or Rails.env.development?) ? 'http' : 'https') + '://' + uri unless uri.include?('://')
182
+
183
+ uri = URI.parse(uri)
184
+ uri.path = ''
185
+ uri.to_s
186
+ end
169
187
  end
170
188
 
171
189
  private
@@ -1,3 +1,3 @@
1
1
  module CoalescingPanda
2
- VERSION = '5.1.12'
2
+ VERSION = '5.2.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.1.12
4
+ version: 5.2.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: 2021-09-03 00:00:00.000000000 Z
13
+ date: 2021-11-22 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rails