instructure/canvas-lms · error

Must be a single Entity

Error message

Must be a single Entity

What it means

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.

Solutions

  1. Provide single-entity metadata, or set idp_entity_id to the exact entityID present inside the aggregate before calling populate_from_metadata_xml
  2. If the IdP changed its entityID, update the provider's idp_entity_id (and entity_id columns) to the new value
  3. Re-download metadata directly for the specific entity rather than the whole aggregate

Example fix

# before
provider.populate_from_metadata_xml(aggregate_xml)
# after
provider.idp_entity_id = 'https://idp.example.com/saml/metadata'
provider.populate_from_metadata_xml(aggregate_xml)
Defensive patterns

Strategy: validation

Validate before calling

entity = SAML2::Entity.parse(xml)
if entity.is_a?(SAML2::Entity::Group)
  raise 'must select one entity' unless provider.idp_entity_id.present?
  raise 'entity id not in metadata' unless entity.any? { |e| e.entity_id == provider.idp_entity_id }
end

Type guard

->(parsed) { parsed.is_a?(SAML2::Entity) }

Try / catch

begin
  provider.populate_from_metadata_xml(xml)
rescue RuntimeError => e
  raise e unless e.message == 'Must be a single Entity'
  # prompt admin to pick a single entity from the aggregate
end

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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

Appendix: source

Thrown at app/models/authentication_provider/saml.rb:334

    when true
      # use ||= to not overwrite a specific algorithm that has otherwise been
      # chosen
      self.sig_alg ||= "RSA-SHA1"
    when false
      self.sig_alg = nil
      # else nil
      # don't change the user settings
    end
  end

  def populate_from_metadata_xml(xml, source: "manual")
    entity = SAML2::Entity.parse(xml)
    raise "Invalid schema" unless entity&.valid_schema?

    if entity.is_a?(SAML2::Entity::Group) && idp_entity_id.present?
      entity = entity.find { |e| e.entity_id == idp_entity_id }
    end
    raise "Must be a single Entity" unless entity.is_a?(SAML2::Entity)

    populate_from_metadata(entity)
    # Only set this after all the above runs so that we catch any issues before overwriting the cached metadata
    settings["metadata"] = xml
    settings["metadata_source"] = source
  end
  alias_method :metadata=, :populate_from_metadata_xml

  def populate_from_metadata_url(url)
    ::Canvas.timeout_protection("saml_metadata_fetch") do
      CanvasHttp.get(url) do |response|
        # raise error unless it's a 2xx
        response.value
        populate_from_metadata_xml(response.body, source: "url")
      end
    end
  end

View on GitHub (pinned to 1c9f0bb801)