canvas-lms/lib/basic_lti/sourcedid.rb

117 lines
4.2 KiB
Ruby

# frozen_string_literal: true
#
# Copyright (C) 2017 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
module BasicLTI
class Sourcedid
SOURCE_ID_REGEX = /^(\d+)-(\d+)-(\d+)-(\d+)-(\w+)$/.freeze
attr_reader :tool, :course, :assignment, :user
def initialize(tool, course, assignment, user)
@tool, @course, @assignment, @user = tool, course, assignment, user
end
def to_s
crypted_token = Canvas::Security.create_encrypted_jwt(
jwt_payload,
self.class.signing_secret,
self.class.encryption_secret
)
Canvas::Security.base64_encode(crypted_token)
end
def jwt_payload
{
iss: "Canvas",
aud: ["Instructure"],
iat: Time.zone.now.to_i,
tool_id: tool.id,
course_id: course.id,
assignment_id: assignment.id,
user_id: user.id,
}
end
private :jwt_payload
def validate!
raise Errors::InvalidSourceId.new("Course is invalid", :course_invalid) unless course
raise Errors::InvalidSourceId.new("User is no longer in course", :user_not_in_course) unless user
raise Errors::InvalidSourceId.new("Assignment is invalid", :assignment_invalid) unless assignment
tag = assignment.external_tool_tag
raise Errors::InvalidSourceId.new("Assignment is no longer associated with this tool", :assignment_tool_mismatch) unless tag &&
(tool.matches_url?(tag.url, false) || tool.matches_tool_domain?(tag.url)) &&
tool.workflow_state != "deleted"
end
def self.load!(sourcedid_string)
raise Errors::InvalidSourceId.new("Invalid sourcedid", :sourcedid_invalid) if sourcedid_string.blank?
token = load_from_legacy_sourcedid!(sourcedid_string) ||
token_from_sourcedid!(sourcedid_string)
tool = ContextExternalTool.find_by(id: token[:tool_id])
course = Course.active.find_by(id: token[:course_id])
if course
user = course.student_enrollments.active.find_by(user_id: token[:user_id])&.user
assignment = course.assignments.active.find_by(id: token[:assignment_id])
end
sourcedid = new(tool, course, assignment, user)
sourcedid.validate!
sourcedid
end
def self.load_from_legacy_sourcedid!(sourcedid)
token = nil
md = sourcedid.match(SOURCE_ID_REGEX)
if md
tool = ContextExternalTool.find_by(id: md[1])
raise Errors::InvalidSourceId.new("Tool is invalid", :tool_invalid) unless tool
new_encoding = [md[1], md[2], md[3], md[4]].join("-")
raise Errors::InvalidSourceId.new("Invalid signature", :signature_invalid) unless Canvas::Security
.verify_hmac_sha1(md[5], new_encoding, key: tool.shard.settings[:encryption_key])
token = { tool_id: md[1].to_i, course_id: md[2], assignment_id: md[3], user_id: md[4] }
end
token
end
def self.token_from_sourcedid!(sourcedid)
Canvas::Security.decrypt_encrypted_jwt(
Canvas::Security.base64_decode(sourcedid),
signing_secret,
encryption_secret
)
rescue JSON::JWT::InvalidFormat
raise Errors::InvalidSourceId.new("Invalid sourcedid", :sourcedid_invalid)
end
def self.signing_secret
DynamicSettings.find["lti-signing-secret"]
end
def self.encryption_secret
DynamicSettings.find["lti-encryption-secret"]
end
end
end