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_panda_session, #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, #start_panda_session!, #url_with_session, #verify_authenticity_token

Instance Method Details

#current_lti_platformObject

[View source]

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

def current_lti_platform
  @current_lti_platform ||= current_panda_session&.lti_platform
end

#current_organizationObject

[View source]

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

def current_organization
  @organization ||= current_session&.panda_pal_organization
  @organization ||= PandaPal::Organization.find_by!(key: organization_key) if organization_key # Deprecated
  @organization ||= PandaPal::Organization.find_by(id: organization_id) if organization_id # Deprecated
  @organization ||= PandaPal::Organization.for_apt_tenant(Apartment::Tenant.current)
end

#forbid_access_if_lacking_sessionObject

[View source]

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

def forbid_access_if_lacking_session
  super
  safari_override
end

#lti_launch_paramsObject

[View source]

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

def lti_launch_params
  current_panda_session[:launch_params]
end

#safari_overrideObject

[View source]

139
140
141
# File 'lib/panda_pal/helpers/controller_helper.rb', line 139

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

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

[View source]

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

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

  organization.switch_tenant(&block)
end

#valid_session?Boolean

Returns:

  • (Boolean)
[View source]

129
130
131
132
133
134
135
136
137
# File 'lib/panda_pal/helpers/controller_helper.rb', line 129

def valid_session?
  return false unless current_panda_session&.persisted?
  return false unless current_organization
  return false unless current_panda_session.panda_pal_organization_id == current_organization.id
  return false unless Apartment::Tenant.current == current_organization.tenant_name
  true
rescue SessionNonceMismatch
  false
end

#validate_launch!(version: :any) ⇒ Object

[View source]

24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/panda_pal/helpers/controller_helper.rb', line 24

def validate_launch!(version: :any)
  safari_override

  version = %i[v1p0 v1p3] if version == :any

  if version.is_a?(Array)
    version = case
    when params[:id_token].present?
      :v1p3
    when params[:oauth_consumer_key].present?
      :v1p0
    end
  end

  valmthd = :"validate_#{version}_launch"
  return send(valmthd) if respond_to?(valmthd)

  render plain: 'Failed to validate LTI launch', status: :unauthorized
end

#validate_v1p0_launchObject

[View source]

44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/panda_pal/helpers/controller_helper.rb', line 44

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: 'Failed to validate LTI v1p0 launch', :status => :unauthorized
    return false
  end

  start_panda_session!
end

#validate_v1p3_launchObject

[View source]

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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/panda_pal/helpers/controller_helper.rb', line 69

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)

  begin
    decoded_jwt.verify!(current_lti_platform.public_jwks)
  rescue JSON::JWK::Set::KidNotFound
    decoded_jwt.verify!(current_lti_platform.public_jwks(force: true))
  end

  raise JSON::JWT::VerificationFailed, 'State is invalid' unless current_panda_session[: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_panda_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