{"record":{"id":"8eb8100d92f5aac0","repo":"instructure/canvas-lms","slug":"must-be-a-single-entity","errorCode":null,"errorMessage":"Must be a single Entity","messagePattern":"Must be a single Entity","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/models/authentication_provider/saml.rb","lineNumber":334,"sourceCode":"    when true\n      # use ||= to not overwrite a specific algorithm that has otherwise been\n      # chosen\n      self.sig_alg ||= \"RSA-SHA1\"\n    when false\n      self.sig_alg = nil\n      # else nil\n      # don't change the user settings\n    end\n  end\n\n  def populate_from_metadata_xml(xml, source: \"manual\")\n    entity = SAML2::Entity.parse(xml)\n    raise \"Invalid schema\" unless entity&.valid_schema?\n\n    if entity.is_a?(SAML2::Entity::Group) && idp_entity_id.present?\n      entity = entity.find { |e| e.entity_id == idp_entity_id }\n    end\n    raise \"Must be a single Entity\" unless entity.is_a?(SAML2::Entity)\n\n    populate_from_metadata(entity)\n    # Only set this after all the above runs so that we catch any issues before overwriting the cached metadata\n    settings[\"metadata\"] = xml\n    settings[\"metadata_source\"] = source\n  end\n  alias_method :metadata=, :populate_from_metadata_xml\n\n  def populate_from_metadata_url(url)\n    ::Canvas.timeout_protection(\"saml_metadata_fetch\") do\n      CanvasHttp.get(url) do |response|\n        # raise error unless it's a 2xx\n        response.value\n        populate_from_metadata_xml(response.body, source: \"url\")\n      end\n    end\n  end\n","sourceCodeStart":316,"sourceCodeEnd":352,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/authentication_provider/saml.rb#L316-L352","documentation":"populate_from_metadata_xml raises \"Must be a single Entity\" when, after parsing, the result is not a SAML2::Entity. A Group (EntitiesDescriptor) whose idp_entity_id is blank, or whose entries do not contain the configured entity_id, cannot be narrowed to one entity.","triggerScenarios":"Saving aggregate metadata (EntitiesDescriptor) without idp_entity_id set; metadata Group does not contain an entity matching the provider's idp_entity_id (IdP rotated its entityID); passing multiple entities at once.","commonSituations":"Admin pastes a federation aggregate; IdP changed its entityID so the stored idp_entity_id no longer matches any entry; new provider created with aggregate metadata and no entity selected.","solutions":["Provide single-entity metadata, or set idp_entity_id to the exact entityID present inside the aggregate before calling populate_from_metadata_xml","If the IdP changed its entityID, update the provider's idp_entity_id (and entity_id columns) to the new value","Re-download metadata directly for the specific entity rather than the whole aggregate"],"exampleFix":"# before\nprovider.populate_from_metadata_xml(aggregate_xml)\n# after\nprovider.idp_entity_id = 'https://idp.example.com/saml/metadata'\nprovider.populate_from_metadata_xml(aggregate_xml)","handlingStrategy":"validation","validationCode":"entity = SAML2::Entity.parse(xml)\nif entity.is_a?(SAML2::Entity::Group)\n  raise 'must select one entity' unless provider.idp_entity_id.present?\n  raise 'entity id not in metadata' unless entity.any? { |e| e.entity_id == provider.idp_entity_id }\nend","typeGuard":"->(parsed) { parsed.is_a?(SAML2::Entity) }","tryCatchPattern":"begin\n  provider.populate_from_metadata_xml(xml)\nrescue RuntimeError => e\n  raise e unless e.message == 'Must be a single Entity'\n  # prompt admin to pick a single entity from the aggregate\nend","preventionTips":["Keep idp_entity_id in sync when the IdP changes its entityID","Prefer per-entity metadata over aggregates","Inspect EntitiesDescriptor contents before importing"],"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"}