{"record":{"id":"0895c04a05e2b560","repo":"instructure/canvas-lms","slug":"must-provide-exactly-one-idpssodescriptor-found-idps-length","errorCode":null,"errorMessage":"Must provide exactly one IDPSSODescriptor; found #{idps.length}","messagePattern":"Must provide exactly one IDPSSODescriptor; found #(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/models/authentication_provider/saml.rb","lineNumber":297,"sourceCode":"    # support using 'false' to disable\n    value = nil if ::Canvas::Plugin.value_to_boolean(value, ignore_unrecognized: true) == false\n\n    unless [nil,\n            SAML2::Bindings::HTTPRedirect::SigAlgs::RSA_SHA1,\n            SAML2::Bindings::HTTPRedirect::SigAlgs::RSA_SHA256].include?(value)\n      errors.add(\"Unsupported signing algorithm #{value}\")\n      return\n    end\n    settings[\"sig_alg\"] = value\n  end\n\n  def self.name_id_formats\n    SAML2::NameID::Format.constants.map { |const| SAML2::NameID::Format.const_get(const, false) }.sort_by(&:downcase)\n  end\n\n  def populate_from_metadata(entity)\n    idps = entity.identity_providers\n    raise \"Must provide exactly one IDPSSODescriptor; found #{idps.length}\" unless idps.length == 1\n\n    idp = idps.first\n    self.idp_entity_id = entity.entity_id\n    self.log_in_url = idp.single_sign_on_services.find { |ep| ep.binding == SAML2::Bindings::HTTPRedirect::URN }.try(:location)\n    self.log_out_url = idp.single_logout_services.find { |ep| ep.binding == SAML2::Bindings::HTTPRedirect::URN }.try(:location)\n    self.certificate_fingerprint = idp.signing_keys.filter_map(&:fingerprint).join(\" \").presence || idp.keys.first&.fingerprint\n\n    recognized_formats = (idp.name_id_formats & self.class.name_id_formats)\n    if recognized_formats.length == 1\n      self.identifier_format = recognized_formats.first\n    elsif identifier_format != SAML2::NameID::Format::UNSPECIFIED &&\n          !recognized_formats.include?(identifier_format)\n      self.identifier_format = SAML2::NameID::Format::UNSPECIFIED\n    end\n\n    settings[:signing_certificates] = idp.signing_keys.filter_map(&:x509)\n    settings[:signing_keys] = idp.signing_keys.filter_map(&:key).map(&:to_s)\n    case idp.want_authn_requests_signed?","sourceCodeStart":279,"sourceCodeEnd":315,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/authentication_provider/saml.rb#L279-L315","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"# before\nprovider.populate_from_metadata_xml(aggregated_federation_xml)\n# after\nentity = SAML2::Entity.parse(aggregated_federation_xml)\nprovider.idp_entity_id = 'https://idp.example.com/saml'\nprovider.populate_from_metadata_xml(aggregated_federation_xml)","handlingStrategy":"validation","validationCode":"entity = SAML2::Entity.parse(xml)\nidps = entity.identity_providers\nraise 'need exactly one IdP' unless idps.length == 1","typeGuard":"null","tryCatchPattern":"begin\n  provider.populate_from_metadata_xml(xml)\nrescue RuntimeError => e\n  Rails.logger.warn(\"SAML metadata rejected: #{e.message}\")\n  flash[:error] = 'Metadata must contain exactly one IdP'\nend","preventionTips":["Use single-IdP metadata URLs, not federation aggregates","Set idp_entity_id before importing aggregate metadata","Count IDPSSODescriptor elements in metadata before upload"],"tags":["ruby","saml","metadata"],"backgroundTag":"unexpected-response-shape","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}