Module: CoalescingPanda::ControllerHelpers

Extended by:
ActiveSupport::Concern
Includes:
SessionReplacement
Defined in:
lib/coalescing_panda/controller_helpers.rb

Instance Method Summary collapse

Methods included from SessionReplacement

#current_session, #current_session_data, #forbid_access_if_lacking_session, #link_nonce, #link_nonce_type, #link_with_session_to, #redirect_with_session_to, #save_session, #session_changed?, #session_expiration_period_minutes, #session_url_for, #url_with_session, #verify_authenticity_token

Instance Method Details

#canvas_api_uriObject



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/coalescing_panda/controller_helpers.rb', line 166

def canvas_api_uri
  @canvas_api_uri ||= begin
    launch_params = current_session_data[:launch_params] || {}
    uri = 'canvas.instructure.com'
    if launch_params[:custom_canvas_api_domain].present?
      uri = launch_params[:custom_canvas_api_domain]
    else
      uri = .settings[:launch_presentation_return_url] || launch_params[:launch_presentation_return_url]
      unless uri.include?('://')
        referrer = URI.parse(request.env["HTTP_REFERER"])
        referrer.path = ''
        uri = [referrer.to_s, uri].join
      end
    end

    uri = ((Rails.env.test? or Rails.env.development?) ? 'http' : 'https') + '://' + uri unless uri.include?('://')

    uri = URI.parse(uri)
    uri.path = ''
    uri.to_s
  end
end

#canvas_environmentObject



157
158
159
160
161
162
163
164
# File 'lib/coalescing_panda/controller_helpers.rb', line 157

def canvas_environment
  case params['custom_test_environment']
    when 'true'
      :test
    else
      :production
  end
end

#canvas_oauth2(*roles) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/coalescing_panda/controller_helpers.rb', line 17

def canvas_oauth2(*roles)
  unless valid_session?
    lti_authorize!(*roles)

    current_session_data['user_id'] = params['user_id']
    current_session_data['lis_person_sourcedid'] = params['lis_person_sourcedid']
    current_session_data['oauth_consumer_key'] = params['oauth_consumer_key']
    current_session_data['custom_canvas_account_id'] = params['custom_canvas_account_id']
  end

  @lti_account = LtiAccount.find_by_key(current_session_data['oauth_consumer_key']) if current_session_data['oauth_consumer_key']

  require_user_api_client
end

#current_lti_accountObject



10
11
12
13
14
# File 'lib/coalescing_panda/controller_helpers.rb', line 10

def 
  @account ||= (CoalescingPanda::LtiAccount.find_by!(key: organization_key) if organization_key)
  @account ||= (CoalescingPanda::LtiAccount.find_by(id: organization_id) if organization_id)
  @account
end

#current_organizationObject



15
# File 'lib/coalescing_panda/controller_helpers.rb', line 15

def current_organization; ; end

#lti_authorize!(*roles) ⇒ Object



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/coalescing_panda/controller_helpers.rb', line 95

def lti_authorize!(*roles)
  if valid_session? # This means that we are returning from an OAuth dance.
    # Set the params as they were at launch to avoid any bait-and-switch attack vulnerabilities in the App's launch controller
    params.merge!(current_session_data[:launch_params])
    return true
  end

  authorized = false
  if (@lti_account = params['oauth_consumer_key'] && LtiAccount.find_by_key(params['oauth_consumer_key']))
    sanitized_params = sanitize_params
    @tp = IMS::LTI::ToolProvider.new(@lti_account.key, @lti_account.secret, sanitized_params)
    authorized = @tp.valid_request?(request)
  end

  logger.info 'not authorized on tp valid request' unless authorized

  if authorized && !(authorized = (roles.count == 0 || (roles & lti_roles).count > 0))
    logger.info 'not authorized on roles'
  end

  if authorized && !(authorized = @lti_account.validate_nonce(params['oauth_nonce'], DateTime.strptime(params['oauth_timestamp'], '%s')))
    logger.info 'not authorized on nonce'
  end

  render :text => 'Invalid Credentials, please contact your Administrator.', :status => :unauthorized unless authorized

  # create session on first launch
  current_session
  authorized
end

#lti_editor_button_response(return_type, return_params) ⇒ Object



145
146
147
148
149
150
151
# File 'lib/coalescing_panda/controller_helpers.rb', line 145

def lti_editor_button_response(return_type, return_params)
  valid_return_types = [:image_url, :iframe, :url, :lti_launch_url]
  raise "invalid editor button return type #{return_type}" unless valid_return_types.include?(return_type)
  return_params[:return_type] = return_type.to_s
  return_url = "#{params['launch_presentation_return_url']}?#{return_params.to_query}"
  redirect_to return_url
end

#lti_rolesObject



153
154
155
# File 'lib/coalescing_panda/controller_helpers.rb', line 153

def lti_roles
  @lti_roles ||= current_session_data[:roles]
end

#refresh_token(api_auth) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
# File 'lib/coalescing_panda/controller_helpers.rb', line 53

def refresh_token(api_auth)
  refresh_client = Bearcat::Client.new(prefix: canvas_api_uri)
  refresh_body = refresh_client.retrieve_token(
    .oauth2_client_id,
    resolve_coalescing_panda_url(:oauth2_redirect_url),
    .oauth2_client_key,
    api_auth.refresh_token,
    'refresh_token'
  )
  api_auth.update({ api_token: refresh_body['access_token'], expires_at: (Time.now + refresh_body['expires_in']) })
end

#render_oauth2_pageObject



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/coalescing_panda/controller_helpers.rb', line 32

def render_oauth2_page
  client_id = .oauth2_client_id
  client = Bearcat::Client.new(prefix: canvas_api_uri)

  state = SecureRandom.hex(32)
  OauthState.create! state_key: state, data: {
    key: current_session_data.dig(:launch_params, :oauth_consumer_key),
    user_id: current_session_data.dig(:launch_params, :user_id),
    api_domain: URI.parse(canvas_api_uri).host,
    api_url: canvas_api_uri,
  }

  @canvas_url = client.auth_redirect_url(client_id, resolve_coalescing_panda_url(:oauth2_redirect_url), { state: state })

  @lti_params = params.to_unsafe_h
  @lti_params.delete('action')
  @lti_params.delete('controller')

  render 'coalescing_panda/oauth2/oauth2', layout: 'coalescing_panda/application'
end

#require_user_api_clientObject



65
66
67
68
69
# File 'lib/coalescing_panda/controller_helpers.rb', line 65

def require_user_api_client
  if user_api_client.nil?
    render_oauth2_page
  end
end

#sanitize_paramsObject

code for method taken from panda_pal v 4.0.8 used for safari workaround



135
136
137
138
139
140
141
142
143
# File 'lib/coalescing_panda/controller_helpers.rb', line 135

def sanitize_params
  sanitized_params = request.request_parameters
  # These params come over with a safari-workaround launch.  The authenticator doesn't like them, so clean them out.
  safe_unexpected_params = ["full_win_launch_requested", "platform_redirect_url"]
  safe_unexpected_params.each do |p|
    sanitized_params.delete(p)
  end
  sanitized_params
end

#user_api_clientObject



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/coalescing_panda/controller_helpers.rb', line 71

def user_api_client
  @user_api_client ||= begin
    user_id = current_session_data.dig(:launch_params, :user_id)
    puri = URI.parse(canvas_api_uri)
    api_auth = CanvasApiAuth.find_by('user_id = ? and api_domain = ?', user_id, puri.host)
    if api_auth
      begin
        refresh_token(api_auth) if api_auth.expired?
        client = Bearcat::Client.new(token: api_auth.api_token, prefix: canvas_api_uri)
        client.('self')
        client
      rescue Footrest::HttpError::BadRequest, Footrest::HttpError::Unauthorized
        # If we can't retrieve our own user profile, or the token refresh fails, something is awry on the canvas end
        # and we'll need to go through the oauth flow again
        nil
      end
    end
  end
end

#valid_session?Boolean

Returns:

  • (Boolean)


126
127
128
129
130
131
# File 'lib/coalescing_panda/controller_helpers.rb', line 126

def valid_session?
  return false unless current_session(create_missing: false)&.persisted?
  true
rescue SessionNonceMismatch
  false
end

#validate_launch!Object



91
92
93
# File 'lib/coalescing_panda/controller_helpers.rb', line 91

def validate_launch!
  lti_authorize!
end