coalescing_panda 5.1.10 → 5.2.0.beta1

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
  SHA256:
3
- metadata.gz: acb564123796d263404c131eb71d372a3592d105d822aa843849be8b36ee3474
4
- data.tar.gz: b5cd0d04fda7b01b3b91098a68d27b4d151737d2ee481eff8e0263795d69f248
3
+ metadata.gz: bee89db63211898d79a2be426960cb091d7d4b9c384b848587c45744e40471e9
4
+ data.tar.gz: 06b1367659b15e63d7811a0a4ed6385f8f8925c3fac3767b9b0283bd00bd78b2
5
5
  SHA512:
6
- metadata.gz: 0f75b1ab2b563eb1d512432859c4e54eb592ccdb59b99aa14920a62b725f1edda6d4c3a47d62b37db2d1de67ed4c382247ad2eebaed33eb3f2e55bd4c89d1cbc
7
- data.tar.gz: fbbef643a7ad94950d3ffd9d54cd11f8808bc0ad4d807227eaff73fff1c0f601ea2ff7e58e59fa09db9507b950f1609c8adb3d555e85503bf0301c5a2c5061ed
6
+ metadata.gz: c902f68f0ed0685039be21864520fe6c80bc199f74afc897b5a38a38e6ea72b335463c0de6498024871e335cb515a7b68c9fa952a384fdf8cb10da12df5e6f23
7
+ data.tar.gz: 6cc22dcad22c6c77a9734b818969356e5600eedef854057d06320b05e1692631e4c814106ac8179a83bb39d2bef1e5396a02df95a6f1144046b09b5eaa739b64
@@ -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
 
@@ -2,7 +2,6 @@ class CreateCoalescingPandaGroupCategories < CoalescingPanda::MiscHelper::Migrat
2
2
  def change
3
3
  create_table :coalescing_panda_group_categories do |t|
4
4
  t.belongs_to :context, polymorphic: true
5
- t.string :context_type
6
5
  t.integer :canvas_group_category_id
7
6
  t.string :name
8
7
 
@@ -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 = [BearcatUri.new(request.env["HTTP_REFERER"]).prefix, launch_presentation_return_url].join unless launch_presentation_return_url.include?('http')
22
- uri = 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
- @lti_params = params.to_hash
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 = 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 = 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,39 @@ module CoalescingPanda
111
93
  end
112
94
 
113
95
  def lti_authorize!(*roles)
96
+ return true if valid_session?
97
+
114
98
  authorized = false
115
99
  if (@lti_account = params['oauth_consumer_key'] && LtiAccount.find_by_key(params['oauth_consumer_key']))
116
100
  sanitized_params = sanitize_params
117
101
  @tp = IMS::LTI::ToolProvider.new(@lti_account.key, @lti_account.secret, sanitized_params)
118
102
  authorized = @tp.valid_request?(request)
119
103
  end
104
+
120
105
  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
106
+
107
+ if authorized && !(authorized = (roles.count == 0 || (roles & lti_roles).count > 0))
108
+ logger.info 'not authorized on roles'
109
+ end
110
+
111
+ if authorized && !(authorized = @lti_account.validate_nonce(params['oauth_nonce'], DateTime.strptime(params['oauth_timestamp'], '%s')))
112
+ logger.info 'not authorized on nonce'
113
+ end
114
+
125
115
  render :text => 'Invalid Credentials, please contact your Administrator.', :status => :unauthorized unless authorized
116
+
126
117
  # create session on first launch
127
118
  current_session
128
119
  authorized
129
120
  end
130
121
 
122
+ def valid_session?
123
+ return false unless current_session(create_missing: false)&.persisted?
124
+ true
125
+ rescue SessionNonceMismatch
126
+ false
127
+ end
128
+
131
129
  # code for method taken from panda_pal v 4.0.8
132
130
  # used for safari workaround
133
131
  def sanitize_params
@@ -161,11 +159,27 @@ module CoalescingPanda
161
159
  end
162
160
  end
163
161
 
164
- def valid_session?
165
- return false unless current_session(create_missing: false)&.persisted?
166
- true
167
- rescue SessionNonceMismatch
168
- false
162
+ def canvas_api_uri
163
+ @canvas_api_uri ||= begin
164
+ launch_params = current_session_data[:launch_params] || {}
165
+ uri = 'canvas.instructure.com'
166
+ if launch_params[:custom_canvas_api_domain].present?
167
+ uri = launch_params[:custom_canvas_api_domain]
168
+ else
169
+ uri = current_lti_account.settings[:launch_presentation_return_url] || launch_params[:launch_presentation_return_url]
170
+ unless uri.include?('://')
171
+ referrer = URI.parse(request.env["HTTP_REFERER"])
172
+ referrer.path = ''
173
+ uri = [referrer.to_s, uri].join
174
+ end
175
+ end
176
+
177
+ uri = ((Rails.env.test? or Rails.env.development?) ? 'http' : 'https') + '://' + uri unless uri.include?('://')
178
+
179
+ uri = URI.parse(uri)
180
+ uri.path = ''
181
+ uri.to_s
182
+ end
169
183
  end
170
184
 
171
185
  private
@@ -1,3 +1,3 @@
1
1
  module CoalescingPanda
2
- VERSION = '5.1.10'
2
+ VERSION = '5.2.0.beta1'
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.10
4
+ version: 5.2.0.beta1
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-07-21 00:00:00.000000000 Z
13
+ date: 2021-11-15 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rails
@@ -590,9 +590,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
590
590
  version: '0'
591
591
  required_rubygems_version: !ruby/object:Gem::Requirement
592
592
  requirements:
593
- - - ">="
593
+ - - ">"
594
594
  - !ruby/object:Gem::Version
595
- version: '0'
595
+ version: 1.3.1
596
596
  requirements: []
597
597
  rubygems_version: 3.0.3
598
598
  signing_key: