Module: PandaPal::Helpers::ControllerHelper

Extended by:
ActiveSupport::Concern
Includes:
SessionReplacement
Defined in:
lib/panda_pal/helpers/controller_helper.rb

Instance Method Summary collapse

Methods included from SessionReplacement

#current_session, #current_session_data, #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

#current_lti_platformObject



15
16
17
# File 'lib/panda_pal/helpers/controller_helper.rb', line 15

def current_lti_platform
  @current_lti_platform ||= current_session(create_missing: false)&.lti_platform
end

#current_organizationObject



9
10
11
12
13
# File 'lib/panda_pal/helpers/controller_helper.rb', line 9

def current_organization
  @organization ||= PandaPal::Organization.find_by!(key: organization_key) if organization_key
  @organization ||= PandaPal::Organization.find_by(id: organization_id) if organization_id
  @organization ||= PandaPal::Organization.find_by_name(Apartment::Tenant.current)
end

#forbid_access_if_lacking_sessionObject



110
111
112
113
# File 'lib/panda_pal/helpers/controller_helper.rb', line 110

def forbid_access_if_lacking_session
  super
  safari_override
end

#lti_launch_paramsObject



19
20
21
# File 'lib/panda_pal/helpers/controller_helper.rb', line 19

def lti_launch_params
  current_session_data[:launch_params]
end

#safari_overrideObject



125
126
127
# File 'lib/panda_pal/helpers/controller_helper.rb', line 125

def safari_override
  use_secure_headers_override(:safari_override) if browser.safari?
end

#switch_tenant(organization = current_organization, &block) ⇒ Object



101
102
103
104
105
106
107
108
# File 'lib/panda_pal/helpers/controller_helper.rb', line 101

def switch_tenant(organization = current_organization, &block)
  return unless organization
  raise 'This method should be called in an around_action callback' unless block_given?

  Apartment::Tenant.switch(organization.name) do
    yield
  end
end

#valid_session?Boolean

Returns:

  • (Boolean)


115
116
117
118
119
120
121
122
123
# File 'lib/panda_pal/helpers/controller_helper.rb', line 115

def valid_session?
  return false unless current_session(create_missing: false)&.persisted?
  return false unless current_organization
  return false unless current_session.panda_pal_organization_id == current_organization.id
  return false unless Apartment::Tenant.current == current_organization.name
  true
rescue SessionNonceMismatch
  false
end

#validate_launch!Object



23
24
25
26
27
28
29
30
31
# File 'lib/panda_pal/helpers/controller_helper.rb', line 23

def validate_launch!
  safari_override

  if params[:id_token].present?
    validate_v1p3_launch
  elsif params[:oauth_consumer_key].present?
    validate_v1p0_launch
  end
end

#validate_v1p0_launchObject



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/panda_pal/helpers/controller_helper.rb', line 33

def validate_v1p0_launch
  authorized = false
  # We should verify the timestamp is recent (within 5 minutes).  The approved timestamp is part of the signature,
  # so we don't need to worry about malicious users messing with it.  We should deny requests that come too long
  # after the approved timestamp.
  good_timestamp = params['oauth_timestamp'] && params['oauth_timestamp'].to_i > Time.now.to_i - 300
  if @organization = good_timestamp && params['oauth_consumer_key'] && PandaPal::Organization.find_by_key(params['oauth_consumer_key'])
    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", "dummy_param"]
    safe_unexpected_params.each do |p|
      sanitized_params.delete(p)
    end
    tp = IMS::LTI::ToolProvider.new(@organization.key, @organization.secret, sanitized_params)
    authorized = tp.valid_request?(request)
  end

  if !authorized
    render plain: 'Invalid Credentials, please contact your Administrator.', :status => :unauthorized unless authorized
  end

  authorized
end

#validate_v1p3_launchObject



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/panda_pal/helpers/controller_helper.rb', line 57

def validate_v1p3_launch
  require "json/jwt"

  decoded_jwt = JSON::JWT.decode(params.require(:id_token), :skip_verification)
  raise JSON::JWT::VerificationFailed, 'error decoding id_token' if decoded_jwt.blank?

  client_id = decoded_jwt['aud']
  deployment_id = decoded_jwt['https://purl.imsglobal.org/spec/lti/claim/deployment_id']

  if deployment_id.present?
    @organization ||= PandaPal::Organization.find_by(key: "#{client_id}/#{deployment_id}")
    @organization ||= PandaPal::Organization.find_by(key: deployment_id)
  end

  @organization ||= PandaPal::Organization.find_by(key: client_id)

  params[:session_key] = params[:state]

  raise JSON::JWT::VerificationFailed, 'Unrecognized Organization' unless @organization.present?
  raise JSON::JWT::VerificationFailed, 'Organization does not trust platform' unless @organization.trusted_platform?(current_lti_platform)

  decoded_jwt.verify!(current_lti_platform.public_jwks)

  raise JSON::JWT::VerificationFailed, 'State is invalid' unless current_session_data[:lti_oauth_nonce] == decoded_jwt['nonce']

  jwt_verifier = PandaPal::LtiJwtValidator.new(decoded_jwt, client_id)
  raise JSON::JWT::VerificationFailed, jwt_verifier.errors unless jwt_verifier.valid?

  current_session.update(panda_pal_organization: @organization)

  @decoded_lti_jwt = decoded_jwt
rescue JSON::JWT::VerificationFailed => e
  payload = Array(e.message)

  render json: {
    message: [
      { errors: payload },
      { id_token: params.require(:id_token) },
    ],
  }, status: :unauthorized

  false
end