instructure/canvas-lms · error
Must provide exactly one IDPSSODescriptor; found #
Error message
Must provide exactly one IDPSSODescriptor; found #{idps.length} What it means
AuthenticationProvider::SAML#populate_from_metadata requires SAML metadata XML to contain exactly one IDPSSODescriptor. If the parsed entity has zero or multiple IdP descriptors, configuration is ambiguous and the provider refuses to populate settings.
Solutions
- Provide metadata containing exactly one IDPSSODescriptor, or ensure idp_entity_id is set so a Group can be narrowed to the matching single entity
- Extract/download the specific IdP entity's metadata from the federation aggregate instead of the whole aggregate
- Validate the metadata XML locally (count IDPSSODescriptor elements) before saving
Example fix
# before provider.populate_from_metadata_xml(aggregated_federation_xml) # after entity = SAML2::Entity.parse(aggregated_federation_xml) provider.idp_entity_id = 'https://idp.example.com/saml' provider.populate_from_metadata_xml(aggregated_federation_xml)
Defensive patterns
Strategy: validation
Validate before calling
entity = SAML2::Entity.parse(xml) idps = entity.identity_providers raise 'need exactly one IdP' unless idps.length == 1
Type guard
null
Try / catch
begin
provider.populate_from_metadata_xml(xml)
rescue RuntimeError => e
Rails.logger.warn("SAML metadata rejected: #{e.message}")
flash[:error] = 'Metadata must contain exactly one IdP'
end Prevention
- Use single-IdP metadata URLs, not federation aggregates
- Set idp_entity_id before importing aggregate metadata
- Count IDPSSODescriptor elements in metadata before upload
When it happens
Trigger: Calling populate_from_metadata (via download_metadata or populate_from_metadata_xml) with metadata containing 0 or 2+ IDPSSODescriptor elements (e.g. an EntitiesDescriptor/Group with several IdPs and no idp_entity_id set, or SP-only metadata).
Common situations: Admin pastes aggregated federation metadata (many IdPs) instead of a single IdP's metadata; uploads metadata XML missing the IdP SSDescriptor; entity_id mismatch causes a Group not to be narrowed to one entity.
Related errors
- Must be a single Entity
- Invalid schema
- A new_id, '# ', referenced an existing # and the # with #…
- A new_integration_id, '#
- A student referenced a non-existent user #
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/0895c04a05e2b560.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/authentication_provider/saml.rb:297
# support using 'false' to disable
value = nil if ::Canvas::Plugin.value_to_boolean(value, ignore_unrecognized: true) == false
unless [nil,
SAML2::Bindings::HTTPRedirect::SigAlgs::RSA_SHA1,
SAML2::Bindings::HTTPRedirect::SigAlgs::RSA_SHA256].include?(value)
errors.add("Unsupported signing algorithm #{value}")
return
end
settings["sig_alg"] = value
end
def self.name_id_formats
SAML2::NameID::Format.constants.map { |const| SAML2::NameID::Format.const_get(const, false) }.sort_by(&:downcase)
end
def populate_from_metadata(entity)
idps = entity.identity_providers
raise "Must provide exactly one IDPSSODescriptor; found #{idps.length}" unless idps.length == 1
idp = idps.first
self.idp_entity_id = entity.entity_id
self.log_in_url = idp.single_sign_on_services.find { |ep| ep.binding == SAML2::Bindings::HTTPRedirect::URN }.try(:location)
self.log_out_url = idp.single_logout_services.find { |ep| ep.binding == SAML2::Bindings::HTTPRedirect::URN }.try(:location)
self.certificate_fingerprint = idp.signing_keys.filter_map(&:fingerprint).join(" ").presence || idp.keys.first&.fingerprint
recognized_formats = (idp.name_id_formats & self.class.name_id_formats)
if recognized_formats.length == 1
self.identifier_format = recognized_formats.first
elsif identifier_format != SAML2::NameID::Format::UNSPECIFIED &&
!recognized_formats.include?(identifier_format)
self.identifier_format = SAML2::NameID::Format::UNSPECIFIED
end
settings[:signing_certificates] = idp.signing_keys.filter_map(&:x509)
settings[:signing_keys] = idp.signing_keys.filter_map(&:key).map(&:to_s)
case idp.want_authn_requests_signed?View on GitHub (pinned to 1c9f0bb801)