Add Lti Registration model
refs INTEROP-7952 flags=none why This commit adds a table and matching ActiveRecord model to facilitate Lti Registrations. This will be further used in building support for Dynamic Registration. test plan: Make sure the migrations run, and the `lti_ims_registrations` table is created. Change-Id: I1d3f6b46d08de7dd68254553191de65fdf72138e Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/313519 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Xander Moffatt <xmoffatt@instructure.com> QA-Review: Xander Moffatt <xmoffatt@instructure.com> Migration-Review: Jacob Burroughs <jburroughs@instructure.com> Product-Review: Paul Gray <paul.gray@instructure.com>
This commit is contained in:
parent
928a86a289
commit
6beb8cdc4b
|
@ -41,6 +41,7 @@ class DeveloperKey < ActiveRecord::Base
|
||||||
|
|
||||||
has_one :tool_consumer_profile, class_name: "Lti::ToolConsumerProfile", inverse_of: :developer_key
|
has_one :tool_consumer_profile, class_name: "Lti::ToolConsumerProfile", inverse_of: :developer_key
|
||||||
has_one :tool_configuration, class_name: "Lti::ToolConfiguration", dependent: :destroy, inverse_of: :developer_key
|
has_one :tool_configuration, class_name: "Lti::ToolConfiguration", dependent: :destroy, inverse_of: :developer_key
|
||||||
|
has_one :lti_registration, class_name: "Lti::IMS::Registration", dependent: :destroy, inverse_of: :developer_key
|
||||||
serialize :scopes, Array
|
serialize :scopes, Array
|
||||||
|
|
||||||
before_validation :normalize_public_jwk_url
|
before_validation :normalize_public_jwk_url
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright (C) 2020 - 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/>.
|
||||||
|
|
||||||
|
class Lti::IMS::Registration < ApplicationRecord
|
||||||
|
self.table_name = "lti_ims_registrations"
|
||||||
|
|
||||||
|
REQUIRED_GRANT_TYPES = ["client_credentials", "implicit"].freeze
|
||||||
|
REQUIRED_RESPONSE_TYPES = ["id_token"].freeze
|
||||||
|
REQUIRED_APPLICATION_TYPE = "web"
|
||||||
|
REQUIRED_TOKEN_ENDPOINT_AUTH_METHOD = "private_key_jwt"
|
||||||
|
|
||||||
|
validates :application_type,
|
||||||
|
:grant_types,
|
||||||
|
:response_types,
|
||||||
|
:redirect_uris,
|
||||||
|
:initiate_login_uri,
|
||||||
|
:client_name,
|
||||||
|
:jwks_uri,
|
||||||
|
:token_endpoint_auth_method,
|
||||||
|
:lti_tool_configuration,
|
||||||
|
:developer_key,
|
||||||
|
presence: true
|
||||||
|
|
||||||
|
validate :required_values_are_present,
|
||||||
|
:redirect_uris_contains_uris,
|
||||||
|
:lti_tool_configuration_is_valid,
|
||||||
|
:scopes_are_valid
|
||||||
|
|
||||||
|
validates :initiate_login_uri,
|
||||||
|
:jwks_uri,
|
||||||
|
:logo_uri,
|
||||||
|
:client_uri,
|
||||||
|
:tos_uri,
|
||||||
|
:policy_uri,
|
||||||
|
format: { with: URI::DEFAULT_PARSER.make_regexp(["http", "https"]) }, allow_blank: true
|
||||||
|
|
||||||
|
belongs_to :developer_key, inverse_of: :lti_registration
|
||||||
|
|
||||||
|
def new_external_tool(context, existing_tool: nil)
|
||||||
|
tool = existing_tool || ContextExternalTool.new(context: context)
|
||||||
|
Importers::ContextExternalToolImporter.import_from_migration(
|
||||||
|
importable_configuration,
|
||||||
|
context,
|
||||||
|
nil,
|
||||||
|
tool,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
tool.developer_key = developer_key
|
||||||
|
tool.workflow_state = "active"
|
||||||
|
tool.use_1_3 = true
|
||||||
|
tool
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def required_values_are_present
|
||||||
|
if (REQUIRED_GRANT_TYPES - grant_types).present?
|
||||||
|
errors.add(:grant_types, "Must include #{REQUIRED_GRANT_TYPES.join(", ")}")
|
||||||
|
end
|
||||||
|
if (REQUIRED_RESPONSE_TYPES - response_types).present?
|
||||||
|
errors.add(:response_types, "Must include #{REQUIRED_RESPONSE_TYPES.join(", ")}")
|
||||||
|
end
|
||||||
|
|
||||||
|
if token_endpoint_auth_method != REQUIRED_TOKEN_ENDPOINT_AUTH_METHOD
|
||||||
|
errors.add(:token_endpoint_auth_method, "Must be 'private_key_jwt'")
|
||||||
|
end
|
||||||
|
|
||||||
|
if application_type != REQUIRED_APPLICATION_TYPE
|
||||||
|
errors.add(:application_type, "Must be 'web'")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def redirect_uris_contains_uris
|
||||||
|
return if redirect_uris.all? { |uri| uri.match? URI::DEFAULT_PARSER.make_regexp(["http", "https"]) }
|
||||||
|
|
||||||
|
errors.add(:redirect_uris, "Must only contain valid URIs")
|
||||||
|
end
|
||||||
|
|
||||||
|
def scopes_are_valid
|
||||||
|
invalid_scopes = scopes - TokenScopes::LTI_SCOPES.keys
|
||||||
|
return if invalid_scopes.empty?
|
||||||
|
|
||||||
|
errors.add(:scopes, "Invalid scopes: #{invalid_scopes.join(", ")}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def lti_tool_configuration_is_valid
|
||||||
|
config_errors = Schemas::Lti::IMS::LtiToolConfiguration.simple_validation_errors(
|
||||||
|
lti_tool_configuration,
|
||||||
|
error_format: :hash
|
||||||
|
)
|
||||||
|
return if config_errors.blank?
|
||||||
|
|
||||||
|
errors.add(
|
||||||
|
:lti_tool_configuration,
|
||||||
|
# Convert errors represented as a Hash to JSON
|
||||||
|
config_errors.is_a?(Hash) ? config_errors.to_json : config_errors
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: this method of only supports message/placement properties defined in
|
||||||
|
# the Dynamic Registration specification. In the future we will need to add
|
||||||
|
# support for all our custom top-level and placement-level properties
|
||||||
|
# ("icon_url", "selection_height", etc.)
|
||||||
|
def importable_configuration
|
||||||
|
{
|
||||||
|
"title" => client_name,
|
||||||
|
"scopes" => scopes,
|
||||||
|
"settings" => {
|
||||||
|
"client_id" => global_developer_key_id
|
||||||
|
}.merge(importable_placements),
|
||||||
|
"public_jwk_url" => jwks_uri,
|
||||||
|
"description" => lti_tool_configuration["description"],
|
||||||
|
"custom_fields" => lti_tool_configuration["custom_parameters"],
|
||||||
|
"target_link_uri" => lti_tool_configuration["target_link_uri"],
|
||||||
|
"oidc_initiation_url" => initiate_login_uri,
|
||||||
|
# TODO: How do we want to handle privacy level?
|
||||||
|
"privacy_level" => "public",
|
||||||
|
"url" => lti_tool_configuration["target_link_uri"],
|
||||||
|
"domain" => lti_tool_configuration["domain"]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def importable_placements
|
||||||
|
lti_tool_configuration["messages"].each_with_object({}) do |message, hash|
|
||||||
|
# In an IMS Tool Registration, a single message can have multiple placements.
|
||||||
|
# To correctly import this, we need to duplicate the message for each desired
|
||||||
|
# placement.
|
||||||
|
message["placements"].each do |placement|
|
||||||
|
hash[placement] = {
|
||||||
|
"custom_fields" => message["custom_parameters"],
|
||||||
|
"message_type" => message["type"],
|
||||||
|
"placement" => placement,
|
||||||
|
"target_link_uri" => message["target_link_uri"],
|
||||||
|
"text" => message["label"]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -17,6 +17,7 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License along
|
# 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/>.
|
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
require "lti_advantage"
|
||||||
|
|
||||||
module Lti
|
module Lti
|
||||||
class ResourcePlacement < ActiveRecord::Base
|
class ResourcePlacement < ActiveRecord::Base
|
||||||
|
@ -36,12 +37,13 @@ module Lti
|
||||||
# Default placements for LTI 1 and LTI 2, ignored for LTI 1.3
|
# Default placements for LTI 1 and LTI 2, ignored for LTI 1.3
|
||||||
LEGACY_DEFAULT_PLACEMENTS = [ASSIGNMENT_SELECTION, LINK_SELECTION].freeze
|
LEGACY_DEFAULT_PLACEMENTS = [ASSIGNMENT_SELECTION, LINK_SELECTION].freeze
|
||||||
|
|
||||||
PLACEMENTS = %i[account_navigation
|
PLACEMENTS_BY_MESSAGE_TYPE = {
|
||||||
similarity_detection
|
LtiAdvantage::Messages::ResourceLinkRequest::MESSAGE_TYPE => %i[
|
||||||
|
account_navigation
|
||||||
assignment_edit
|
assignment_edit
|
||||||
assignment_menu
|
|
||||||
assignment_index_menu
|
|
||||||
assignment_group_menu
|
assignment_group_menu
|
||||||
|
assignment_index_menu
|
||||||
|
assignment_menu
|
||||||
assignment_selection
|
assignment_selection
|
||||||
assignment_view
|
assignment_view
|
||||||
collaboration
|
collaboration
|
||||||
|
@ -50,11 +52,10 @@ module Lti
|
||||||
course_home_sub_navigation
|
course_home_sub_navigation
|
||||||
course_navigation
|
course_navigation
|
||||||
course_settings_sub_navigation
|
course_settings_sub_navigation
|
||||||
discussion_topic_menu
|
|
||||||
discussion_topic_index_menu
|
discussion_topic_index_menu
|
||||||
editor_button
|
discussion_topic_menu
|
||||||
file_menu
|
|
||||||
file_index_menu
|
file_index_menu
|
||||||
|
file_menu
|
||||||
global_navigation
|
global_navigation
|
||||||
homework_submission
|
homework_submission
|
||||||
link_selection
|
link_selection
|
||||||
|
@ -62,18 +63,47 @@ module Lti
|
||||||
module_group_menu
|
module_group_menu
|
||||||
module_index_menu
|
module_index_menu
|
||||||
module_index_menu_modal
|
module_index_menu_modal
|
||||||
module_menu
|
|
||||||
module_menu_modal
|
module_menu_modal
|
||||||
|
module_menu
|
||||||
post_grades
|
post_grades
|
||||||
quiz_menu
|
|
||||||
quiz_index_menu
|
quiz_index_menu
|
||||||
|
quiz_menu
|
||||||
resource_selection
|
resource_selection
|
||||||
submission_type_selection
|
similarity_detection
|
||||||
student_context_card
|
student_context_card
|
||||||
|
submission_type_selection
|
||||||
tool_configuration
|
tool_configuration
|
||||||
user_navigation
|
user_navigation
|
||||||
wiki_index_menu
|
wiki_index_menu
|
||||||
wiki_page_menu].freeze
|
wiki_page_menu
|
||||||
|
],
|
||||||
|
LtiAdvantage::Messages::DeepLinkingRequest::MESSAGE_TYPE => %i[
|
||||||
|
assignment_index_menu
|
||||||
|
assignment_menu
|
||||||
|
assignment_selection
|
||||||
|
collaboration
|
||||||
|
conference_selection
|
||||||
|
discussion_topic_index_menu
|
||||||
|
discussion_topic_menu
|
||||||
|
editor_button
|
||||||
|
file_menu
|
||||||
|
homework_submission
|
||||||
|
link_selection
|
||||||
|
migration_selection
|
||||||
|
module_index_menu
|
||||||
|
module_index_menu_modal
|
||||||
|
module_menu_modal
|
||||||
|
module_menu
|
||||||
|
quiz_index_menu
|
||||||
|
quiz_menu
|
||||||
|
resource_selection
|
||||||
|
submission_type_selection
|
||||||
|
wiki_index_menu
|
||||||
|
wiki_page_menu
|
||||||
|
]
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
PLACEMENTS = PLACEMENTS_BY_MESSAGE_TYPE.values.flatten.uniq.freeze
|
||||||
|
|
||||||
PLACEMENT_LOOKUP = {
|
PLACEMENT_LOOKUP = {
|
||||||
"Canvas.placements.accountNavigation" => ACCOUNT_NAVIGATION,
|
"Canvas.placements.accountNavigation" => ACCOUNT_NAVIGATION,
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright (C) 2020 - 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/>.
|
||||||
|
|
||||||
|
class CreateLtiIMSRegistrations < ActiveRecord::Migration[7.0]
|
||||||
|
tag :predeploy
|
||||||
|
|
||||||
|
def up
|
||||||
|
create_table :lti_ims_registrations do |t|
|
||||||
|
t.jsonb :lti_tool_configuration, null: false
|
||||||
|
t.references :developer_key, null: false, foreign_key: true, index: true
|
||||||
|
t.string :application_type, null: false
|
||||||
|
t.text :grant_types, array: true, default: [], null: false
|
||||||
|
t.text :response_types, array: true, default: [], null: false
|
||||||
|
t.text :redirect_uris, array: true, default: [], null: false
|
||||||
|
t.text :initiate_login_uri, null: false
|
||||||
|
t.string :client_name, null: false
|
||||||
|
t.text :jwks_uri, null: false
|
||||||
|
t.text :logo_uri
|
||||||
|
t.string :token_endpoint_auth_method, null: false
|
||||||
|
t.string :contacts, array: true, default: [], null: false, limit: 255
|
||||||
|
t.text :client_uri
|
||||||
|
t.text :policy_uri
|
||||||
|
t.text :tos_uri
|
||||||
|
t.text :scopes, array: true, default: [], null: false
|
||||||
|
|
||||||
|
t.references :root_account, foreign_key: { to_table: :accounts }, null: false, index: false
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_replica_identity "Lti::IMS::Registration", :root_account_id, 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
drop_table :lti_ims_registrations
|
||||||
|
end
|
||||||
|
end
|
|
@ -22,12 +22,21 @@ module Schemas
|
||||||
class Base
|
class Base
|
||||||
delegate :validate, :valid?, to: :schema_checker
|
delegate :validate, :valid?, to: :schema_checker
|
||||||
|
|
||||||
def self.simple_validation_errors(json_hash)
|
def self.simple_validation_errors(json_hash, error_format: :string)
|
||||||
error = new.validate(json_hash).to_a.first
|
error = new.validate(json_hash).to_a.first
|
||||||
return nil if error.blank?
|
return nil if error.blank?
|
||||||
|
|
||||||
if error["data_pointer"].present?
|
if error["data_pointer"].present?
|
||||||
|
if error_format == :hash
|
||||||
|
return {
|
||||||
|
error: error["data"],
|
||||||
|
field: error["data_pointer"],
|
||||||
|
schema: error["schema"]
|
||||||
|
}
|
||||||
|
else
|
||||||
return "#{error["data"]} #{error["data_pointer"]}. Schema: #{error["schema"]}"
|
return "#{error["data"]} #{error["data_pointer"]}. Schema: #{error["schema"]}"
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
"The following fields are required: #{error.dig("schema", "required").join(", ")}"
|
"The following fields are required: #{error.dig("schema", "required").join(", ")}"
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright (C) 2020 - 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/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
# Schema for a https://purl.imsglobal.org/spec/lti-tool-configuration
|
||||||
|
module Schemas::Lti::IMS
|
||||||
|
class LtiToolConfiguration < Schemas::Base
|
||||||
|
SCHEMA = {
|
||||||
|
"type" => "object",
|
||||||
|
"required" => %w[
|
||||||
|
domain
|
||||||
|
messages
|
||||||
|
claims
|
||||||
|
].freeze,
|
||||||
|
"properties" => {
|
||||||
|
"domain" => { "type" => "string" }.freeze,
|
||||||
|
"secondary_domains" => {
|
||||||
|
"type" => "array",
|
||||||
|
"items" => {
|
||||||
|
"type" => "string"
|
||||||
|
}.freeze
|
||||||
|
}.freeze,
|
||||||
|
"target_link_uri" => { "type" => "string" }.freeze,
|
||||||
|
"custom_parameters" => {
|
||||||
|
"type" => "object",
|
||||||
|
"additionalProperties" => {
|
||||||
|
"type" => "string"
|
||||||
|
}.freeze
|
||||||
|
}.freeze,
|
||||||
|
"description" => { "type" => "string" }.freeze,
|
||||||
|
"messages" => {
|
||||||
|
"type" => "array",
|
||||||
|
"items" => {
|
||||||
|
"type" => "object",
|
||||||
|
"required" => [
|
||||||
|
"type"
|
||||||
|
].freeze,
|
||||||
|
"properties" => {
|
||||||
|
"type" => {
|
||||||
|
"type" => "string",
|
||||||
|
"enum" => Lti::ResourcePlacement::PLACEMENTS_BY_MESSAGE_TYPE.keys
|
||||||
|
}.freeze,
|
||||||
|
"target_link_uri" => { "type" => "string" }.freeze,
|
||||||
|
"label" => { "type" => "string" }.freeze,
|
||||||
|
"icon_uri" => { "type" => "string" }.freeze,
|
||||||
|
"custom_parameters" => {
|
||||||
|
"type" => "object",
|
||||||
|
"additionalProperties" => {
|
||||||
|
"type" => "string"
|
||||||
|
}.freeze
|
||||||
|
}.freeze,
|
||||||
|
"placements" => {
|
||||||
|
"type" => "array",
|
||||||
|
"items" => {
|
||||||
|
"type" => "string",
|
||||||
|
"enum" => Lti::ResourcePlacement::PLACEMENTS.map(&:to_s)
|
||||||
|
}.freeze
|
||||||
|
}.freeze,
|
||||||
|
}.freeze
|
||||||
|
}
|
||||||
|
}.freeze,
|
||||||
|
"claims" => {
|
||||||
|
"type" => "array",
|
||||||
|
"items" => {
|
||||||
|
"type" => "string"
|
||||||
|
}.freeze
|
||||||
|
}.freeze,
|
||||||
|
}.freeze
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
TYPE = "https://purl.imsglobal.org/spec/lti-tool-configuration"
|
||||||
|
|
||||||
|
def schema
|
||||||
|
SCHEMA
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,242 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright (C) 2023 - 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 Lti::IMS
|
||||||
|
describe Registration do
|
||||||
|
let(:application_type) { :web }
|
||||||
|
let(:grant_types) { [:client_credentials, :implicit] }
|
||||||
|
let(:response_types) { [:id_token] }
|
||||||
|
let(:redirect_uris) { ["http://example.com"] }
|
||||||
|
let(:initiate_login_uri) { "http://example.com/login" }
|
||||||
|
let(:client_name) { "Example Tool" }
|
||||||
|
let(:jwks_uri) { "http://example.com/jwks" }
|
||||||
|
let(:logo_uri) { "http://example.com/logo.png" }
|
||||||
|
let(:client_uri) { "http://example.com/" }
|
||||||
|
let(:tos_uri) { "http://example.com/tos" }
|
||||||
|
let(:policy_uri) { "http://example.com/policy" }
|
||||||
|
let(:token_endpoint_auth_method) { "private_key_jwt" }
|
||||||
|
let(:lti_tool_configuration) do
|
||||||
|
{
|
||||||
|
domain: "example.com",
|
||||||
|
messages: [],
|
||||||
|
claims: []
|
||||||
|
}
|
||||||
|
end
|
||||||
|
let(:scopes) { [] }
|
||||||
|
|
||||||
|
let(:registration) do
|
||||||
|
r = Registration.new({
|
||||||
|
application_type: application_type,
|
||||||
|
grant_types: grant_types,
|
||||||
|
response_types: response_types,
|
||||||
|
redirect_uris: redirect_uris,
|
||||||
|
initiate_login_uri: initiate_login_uri,
|
||||||
|
client_name: client_name,
|
||||||
|
jwks_uri: jwks_uri,
|
||||||
|
logo_uri: logo_uri,
|
||||||
|
client_uri: client_uri,
|
||||||
|
tos_uri: tos_uri,
|
||||||
|
policy_uri: policy_uri,
|
||||||
|
token_endpoint_auth_method: token_endpoint_auth_method,
|
||||||
|
lti_tool_configuration: lti_tool_configuration,
|
||||||
|
scopes: scopes
|
||||||
|
}.compact)
|
||||||
|
r.developer_key = developer_key
|
||||||
|
r
|
||||||
|
end
|
||||||
|
let(:developer_key) { DeveloperKey.create }
|
||||||
|
|
||||||
|
describe "validations" do
|
||||||
|
subject { registration.validate }
|
||||||
|
|
||||||
|
context "when valid" do
|
||||||
|
it { is_expected.to eq true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "application_type" do
|
||||||
|
context "is \"web\"" do
|
||||||
|
it { is_expected.to eq true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "is not \"web\"" do
|
||||||
|
let(:application_type) { "native" }
|
||||||
|
|
||||||
|
it { is_expected.to eq false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "is not included" do
|
||||||
|
let(:application_type) { nil }
|
||||||
|
|
||||||
|
it { is_expected.to eq false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "grant_types" do
|
||||||
|
context "includes other types" do
|
||||||
|
let(:grant_types) { %i[client_credentials implicit foo bar] }
|
||||||
|
|
||||||
|
it { is_expected.to eq true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "does not include implicit" do
|
||||||
|
let(:grant_types) { [:client_credentials, :foo] }
|
||||||
|
|
||||||
|
it { is_expected.to eq false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "does not include client_credentials" do
|
||||||
|
let(:grant_types) { [:implicit, :foo] }
|
||||||
|
|
||||||
|
it { is_expected.to eq false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "response_types" do
|
||||||
|
context "includes other types" do
|
||||||
|
let(:response_types) { %i[id_token foo bar] }
|
||||||
|
|
||||||
|
it { is_expected.to eq true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "is not included" do
|
||||||
|
let(:response_types) { nil }
|
||||||
|
|
||||||
|
it { is_expected.to eq false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "does not include id_token" do
|
||||||
|
let(:response_types) { [:foo, :bar] }
|
||||||
|
|
||||||
|
it { is_expected.to eq false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "redirect_uris" do
|
||||||
|
context "includes valid uris" do
|
||||||
|
let(:redirect_uris) { ["https://example.com", "https://example.com/foo"] }
|
||||||
|
|
||||||
|
it { is_expected.to eq true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "is not included" do
|
||||||
|
let(:redirect_uris) { nil }
|
||||||
|
|
||||||
|
it { is_expected.to eq false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "includes a non-url" do
|
||||||
|
let(:redirect_uris) { ["https://example.com", "asdf"] }
|
||||||
|
|
||||||
|
it { is_expected.to eq false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "initiate_login_uri" do
|
||||||
|
context "is not included" do
|
||||||
|
let(:initiate_login_uri) { nil }
|
||||||
|
|
||||||
|
it { is_expected.to eq false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "is a valid uri" do
|
||||||
|
let(:initiate_login_uri) { "http://example.com/login" }
|
||||||
|
|
||||||
|
it { is_expected.to eq true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "is not a valid uri" do
|
||||||
|
let(:initiate_login_uri) { "asdf" }
|
||||||
|
|
||||||
|
it { is_expected.to eq false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "client_name" do
|
||||||
|
context "is not included" do
|
||||||
|
let(:client_name) { nil }
|
||||||
|
|
||||||
|
it { is_expected.to eq false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "jwks_uri" do
|
||||||
|
context "is not included" do
|
||||||
|
let(:jwks_uri) { nil }
|
||||||
|
|
||||||
|
it { is_expected.to eq false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "is not a valid uri" do
|
||||||
|
let(:jwks_uri) { "asdf" }
|
||||||
|
|
||||||
|
it { is_expected.to eq false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "token_endpoint_auth_method" do
|
||||||
|
context "is not \"private_key_jwt\"" do
|
||||||
|
let(:token_endpoint_auth_method) { "asdf" }
|
||||||
|
|
||||||
|
it { is_expected.to eq false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "logo_uri" do
|
||||||
|
context "is not a valid uri" do
|
||||||
|
let(:logo_uri) { "asdf" }
|
||||||
|
|
||||||
|
it { is_expected.to eq false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "client_uri" do
|
||||||
|
context "is not a valid uri" do
|
||||||
|
let(:client_uri) { "asdf" }
|
||||||
|
|
||||||
|
it { is_expected.to eq false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "tos_uri" do
|
||||||
|
context "is not a valid uri" do
|
||||||
|
let(:tos_uri) { "asdf" }
|
||||||
|
|
||||||
|
it { is_expected.to eq false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "policy_uri" do
|
||||||
|
context "is not a valid uri" do
|
||||||
|
let(:policy_uri) { "asdf" }
|
||||||
|
|
||||||
|
it { is_expected.to eq false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "scopes" do
|
||||||
|
context "contains invalid scopes" do
|
||||||
|
let(:scopes) { ["asdf"] }
|
||||||
|
|
||||||
|
it { is_expected.to eq false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue