instructure/canvas-lms · error · CCImportError

Invalid XML Configuration

Error message

Invalid XML Configuration

What it means

convert_blti_xml validates that the parsed BLTI/LTI configuration XML carries an imsglobal namespace before importing. If the parsed document's namespaces do not include 'imsglobal', the XML is not recognized as a valid LTI tool configuration and a CCImportError is raised.

Solutions

  1. Inspect the fetched XML and ensure the root element declares an imsglobal namespace (e.g. <basic_lti_link xmlns="http://www.imsglobal.org/xsd/imslticc_v1p0">)
  2. Fetch the configuration XML manually (curl) and confirm the tool provider is returning real LTI config, not an error/login page
  3. Obtain a fresh/correct configuration XML URL from the tool vendor

Example fix

# before (no namespace)
<cartridge_basiclti_link>
  ...
</cartridge_basiclti_link>

# after
<cartridge_basiclti_link xmlns="http://www.imsglobal.org/xsd/imslticc_v1p0"
    xmlns:blti="http://www.imsglobal.org/xsd/imsbasiclti_v1p0">
  ...
</cartridge_basiclti_link>
Defensive patterns

Strategy: validation

Validate before calling

doc = Nokogiri::XML(xml)
raise CCImportError, 'not LTI config' unless doc.namespaces.any? { |_, v| v.downcase.include?('imsglobal') }

Type guard

def lti_config_xml?(xml)
  doc = Nokogiri::XML(xml) { |c| c.norecover }
  doc.namespaces.values.join.downcase.include?('imsglobal')
rescue Nokogiri::XML::SyntaxError
  false
end

Try / catch

begin
  tool = converter.convert_blti_xml(xml)
rescue CCImportError => e
  Rails.logger.warn("BLTI import rejected: #{e.message}")
  flash[:error] = I18n.t('invalid_tool_config')
end

Prevention

When it happens

Trigger: Calling convert_blti_xml (directly or via retrieve_and_convert_blti_url) with XML whose root/declared namespaces lack 'imsglobal' — e.g. plain XML, wrong namespace URI, or an HTML error page that happens to parse as XML.

Common situations: A tool provider URL returns a non-LTI XML document or error page; the config XML was hand-edited dropping the xmlns declaration; the vendor uses an outdated/incorrect namespace; the fetched content is a login/redirect page.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/89b27929c10603cd. Report an issue: GitHub.

Appendix: source

Thrown at lib/cc/importer/blti_converter.rb:113

          if (tool[:assignment_points_possible] = ext[:custom_fields].delete("outcome"))
            tool[:assignment_points_possible] = tool[:assignment_points_possible].to_f
          end
          tool[:settings] = ext[:custom_fields]
        else
          tool[:extensions] << ext
        end
      end
      if (icon = get_node_val(doc, "#{link_css_path} > #{blti}|icon"))
        tool[:settings] ||= {}
        tool[:settings][:icon_url] = icon.strip
      end
      tool
    end

    def convert_blti_xml(xml)
      doc = create_xml_doc(xml)
      unless doc.namespaces.to_s.downcase.include? "imsglobal"
        raise CCImportError, I18n.t("Invalid XML Configuration")
      end

      begin
        tool = convert_blti_link(doc)
        check_for_unescaped_url_properties(tool) if tool
      rescue Nokogiri::XML::XPath::SyntaxError
        raise CCImportError, I18n.t(:invalid_xml_syntax, "Invalid xml syntax")
      end
      tool
    end

    def check_for_unescaped_url_properties(obj)
      # Recursively look for properties named 'url'
      case obj
      when Hash
        obj.select { |k, v| k.to_s == "url" && v.is_a?(String) }
           .each_value { |v| check_for_unescaped_url(v) }
        obj.each_value { |v| check_for_unescaped_url_properties(v) }

View on GitHub (pinned to 1c9f0bb801)