Skip to content

Commit

Permalink
Merge pull request #173 from OpenDSA/lti13
Browse files Browse the repository at this point in the history
Adding LTI 1.3 Support
  • Loading branch information
babz007 authored Sep 10, 2024
2 parents f1e322a + 048c6e5 commit bd1da89
Show file tree
Hide file tree
Showing 31 changed files with 1,386 additions and 549 deletions.
4 changes: 3 additions & 1 deletion app/admin/lms_access.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
end

menu label: "LMS Accesses", parent: 'LMS config', priority: 30
permit_params :lms_instance_id, :user_id, :access_token
permit_params :lms_instance_id, :user_id, :access_token, :consumer_key

index do
id_column
Expand All @@ -45,6 +45,7 @@
link_to c.access_token, admin_lms_access_path(c)
end
# column :created_at
column :consumer_key
actions
end

Expand All @@ -56,6 +57,7 @@
f.input :user, collection: User.all.order(:first_name, :last_name)
end
f.input :access_token
f.input :consumer_key
end
f.actions
end
Expand Down
23 changes: 20 additions & 3 deletions app/admin/lms_instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@
# consumer_key :string(255)
# consumer_secret :string(255)
# organization_id :bigint
# client_id :string
# private_key :text
# public_key :text
# keyset_url :string
# oauth2_url :string
# platform_oidc_auth_url :string
# issuer :string
#
# Indexes
#
Expand All @@ -21,7 +28,9 @@
includes :lms_type, :organization

menu label: "LMS Instances",parent: 'LMS config', priority: 20
permit_params :url, :lms_type_id, :organization_id
# permit_params :url, :lms_type_id, :organization_id
permit_params :url, :lms_type_id, :organization_id, :client_id, :private_key, :public_key, :keyset_url, :oauth2_url, :platform_oidc_auth_url, :issuer


index do
id_column
Expand All @@ -31,6 +40,14 @@
# column :consumer_key
# column :consumer_secret
column :created_at
column :client_id
# Consider if you really want to display keys and secrets here
# column :private_key
# column :public_key
column :keyset_url
column :oauth2_url
column :platform_oidc_auth_url
column :issuer
actions
end

Expand All @@ -43,8 +60,8 @@
f.input :consumer_key
f.input :consumer_secret
f.input :client_id
f.input :private_key
f.input :public_key
f.input :private_key, as: :text
f.input :public_key, as: :text
f.input :keyset_url
f.input :oauth2_url
f.input :platform_oidc_auth_url
Expand Down
20 changes: 18 additions & 2 deletions app/controllers/inst_books_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,25 @@ class InstBooksController < ApplicationController
def compile
host_port = request.protocol + request.host_with_port
extrtool_launch_base_url = host_port + "/lti/launch_extrtool"
inst_book = InstBook.find(params[:id])
course_offering = inst_book.course_offering
lms_instance = course_offering.lms_instance

# Determine LTI version from the LmsInstance
lti_version = lms_instance.lti_version
puts "Determined LTI version: #{lti_version}"
# Set URLs based on the LTI version
if params[:operation] == 'generate_course'
launch_url = host_port + "/lti/launch"
resource_selection_url = host_port + "/lti/resource"
if lti_version == 'LTI-1p0'
launch_url = host_port + "/lti/launch"
resource_selection_url = host_port + "/lti/resource"
elsif lti_version == 'LTI-1p3'
launch_url = host_port + "/lti13/launches"
resource_selection_url = host_port + "/lti13/deep_linking/content_selection"
else
render plain: "Unsupported LTI version", status: :unprocessable_entity
return
end
@job = Delayed::Job.enqueue GenerateCourseJob.new(params[:id], launch_url, resource_selection_url,
extrtool_launch_base_url, current_user.id)
else
Expand Down
119 changes: 74 additions & 45 deletions app/controllers/lti13/deep_link_launches_controller.rb
Original file line number Diff line number Diff line change
@@ -1,48 +1,77 @@
class Lti13::DeepLinkLaunchesController < ApplicationController
before_action :set_tool
skip_before_action only: :create

# POST lti/tools/#/deep_link_launches
# Not much different than LTI launch endpoint inside a simple reference implementation but you
# should have a diff endpoint for deeplinks than your LTI resource link request for single responsiblity
def create
if params[:id_token]&.present?
@decoded_header = Jwt::Header.new(params[:id_token]).call
kid = @decoded_header['kid']

@decoded_jwt = Lti13Service::DecodePlatformJwt.new(@tool, params[:id_token], kid).call
@launch = @tool.launches.build(jwt: params[:id_token], decoded_jwt: @decoded_jwt ? @decoded_jwt.first : nil, state: params[:state])
end

@launch ||= Launch.new
respond_to do |format|
if @launch.save
format.html { redirect_to [:lti, @tool, @launch], notice: 'Successful Launch.' }
format.json { render :show, status: :created, location: @launch }
else
format.html { render json: 'Invalid Launch', status: :unprocessable_entity }
format.json { render json: @launch.errors, status: :unprocessable_entity }
end
end
end

# GET lti/tools/#/deep_link_launch/*launch_id*
# page that allows user to select content
def show
@launch = Launch.find(params[:id])
before_action :set_tool
skip_before_action only: :create

# POST lti/tools/#/deep_link_launches
# Handles the creation of a deep link launch
def create
if params[:id_token]&.present?
@decoded_header = Jwt::Header.new(params[:id_token]).call
kid = @decoded_header['kid']

@decoded_jwt = Lti13Service::DecodePlatformJwt.new(@tool, params[:id_token], kid).call
@launch = @tool.launches.build(jwt: params[:id_token], decoded_jwt: @decoded_jwt ? @decoded_jwt.first : nil, state: params[:state])
end

# GET lti/tools/#/deep_link_launch/*launch_id*/launch
# takes selected content and launches back to platform with JWT
def launch
@launch = Launch.find(params[:deep_link_launch_id])
@form_url = @launch.decoded_jwt[Rails.configuration.lti_claims_and_scopes['deep_linking_claim']]['deep_link_return_url']
@deep_link_jwt = Lti13Service::DeepLinkJwt.new(@launch, lti_tool_launches_url(@tool), params[:content_items])
end

private
def set_tool
@tool = Tool.find_by_id(params[:tool_id])
render json: { error: 'Tool not found' }, status: :not_found unless @tool

@launch ||= Launch.new
respond_to do |format|
if @launch.save
format.html { redirect_to [:lti13, @tool, @launch], notice: 'Successful Launch.' }
format.json { render :show, status: :created, location: @launch }
else
format.html { render json: 'Invalid Launch', status: :unprocessable_entity }
format.json { render json: @launch.errors, status: :unprocessable_entity }
end
end
end
end

# GET lti/tools/#/deep_link_launch/*launch_id*
# allows user to select content
def show
@launch = Launch.find(params[:id])
end

# GET lti/tools/#/deep_link_launch/*launch_id*/launch
# takes selected content and launches back to platform with JWT
def launch
@launch = Launch.find(params[:deep_link_launch_id])
@form_url = @launch.decoded_jwt[Rails.configuration.lti_claims_and_scopes['deep_linking_claim']]['deep_link_return_url']
@deep_link_jwt = Lti13Service::DeepLinkJwt.new(@launch, lti_tool_launches_url(@tool), params[:content_items])
end

# GET lti13/deep_linking/content_selection
def content_selection
@launch_url = request.protocol + request.host_with_port + "/lti13/launches"
module_info = InstModule.get_current_versions_dict()
@json = module_info.to_json

Rails.logger.info "Launch URL: #{@launch_url}"
Rails.logger.debug "Module Info JSON: #{@json.inspect}"
render 'resource', layout: 'lti_resource'
end

# POST lti13/deep_linking/content_selected
def content_selected
@launch = Launch.find(params[:launch_id])
@form_url = @launch.decoded_jwt[Rails.configuration.lti_claims_and_scopes['deep_linking_claim']]['deep_link_return_url']
selected_content = params[:selected_content]
Rails.logger.info "Selected Content: #{selected_content}"

deep_link_jwt_service = Lti13Service::DeepLinkJwt.new(@launch, selected_content)
deep_link_jwt = deep_link_jwt_service.call
Rails.logger.info "Deep Link JWT: #{deep_link_jwt}"

# Return the selected content to LMS
redirect_to "#{@form_url}?JWT=#{deep_link_jwt}"
end

#~ Private methods ..........................................................

private
# -------------------------------------------------------------

def set_tool
@tool = Tool.find_by_id(params[:tool_id])
render json: { error: 'Tool not found' }, status: :not_found unless @tool
end
end
Loading

0 comments on commit bd1da89

Please sign in to comment.