lib/saml/kit/metadata.rb in saml-kit-1.0.9 vs lib/saml/kit/metadata.rb in saml-kit-1.0.10

- old
+ new

@@ -1,5 +1,7 @@ +# frozen_string_literal: true + module Saml module Kit # The Metadata object can be used to parse an XML string of metadata. # # metadata = Saml::Kit::Metadata.from(raw_xml) @@ -27,15 +29,15 @@ include XsdValidatable include Translatable include Buildable METADATA_XSD = File.expand_path('./xsd/saml-schema-metadata-2.0.xsd', File.dirname(__FILE__)).freeze NAMESPACES = { - "NameFormat": Namespaces::ATTR_SPLAT, - "ds": ::Xml::Kit::Namespaces::XMLDSIG, - "md": Namespaces::METADATA, - "saml": Namespaces::ASSERTION, - "samlp": Namespaces::PROTOCOL, + NameFormat: Namespaces::ATTR_SPLAT, + ds: ::Xml::Kit::Namespaces::XMLDSIG, + md: Namespaces::METADATA, + saml: Namespaces::ASSERTION, + samlp: Namespaces::PROTOCOL, }.freeze validates_presence_of :metadata validate :must_contain_descriptor validate :must_match_xsd @@ -48,40 +50,38 @@ @xml = xml end # Returns the /EntityDescriptor/@entityID def entity_id - document.find_by('/md:EntityDescriptor/@entityID').value + at_xpath('/md:EntityDescriptor/@entityID').try(:value) end # Returns the supported NameIDFormats. def name_id_formats - document.find_all("/md:EntityDescriptor/md:#{name}/md:NameIDFormat").map(&:text) + search("/md:EntityDescriptor/md:#{name}/md:NameIDFormat").map(&:text) end # Returns the Organization Name def organization_name - document.find_by('/md:EntityDescriptor/md:Organization/md:OrganizationName').try(:text) + at_xpath('/md:EntityDescriptor/md:Organization/md:OrganizationName').try(:text) end # Returns the Organization URL def organization_url - document.find_by('/md:EntityDescriptor/md:Organization/md:OrganizationURL').try(:text) + at_xpath('/md:EntityDescriptor/md:Organization/md:OrganizationURL').try(:text) end # Returns the Company def contact_person_company - document.find_by('/md:EntityDescriptor/md:ContactPerson/md:Company').try(:text) + at_xpath('/md:EntityDescriptor/md:ContactPerson/md:Company').try(:text) end # Returns each of the X509 certificates. def certificates - @certificates ||= document.find_all("/md:EntityDescriptor/md:#{name}/md:KeyDescriptor").map do |item| - cert = item.at_xpath('./ds:KeyInfo/ds:X509Data/ds:X509Certificate', NAMESPACES).text - attribute = item.attribute('use') - use = attribute.nil? ? nil : item.attribute('use').value - ::Xml::Kit::Certificate.new(cert, use: use) + @certificates ||= search("/md:EntityDescriptor/md:#{name}/md:KeyDescriptor").map do |item| + cert = item.at_xpath('./ds:KeyInfo/ds:X509Data/ds:X509Certificate', 'ds' => ::Xml::Kit::Namespaces::XMLDSIG).try(:text) + ::Xml::Kit::Certificate.new(cert, use: item.attribute('use').try(:value)) end end # Returns the encryption certificates def encryption_certificates @@ -95,11 +95,11 @@ # Returns each of the service endpoints supported by this metadata. # # @param type [String] the type of service. .E.g. `AssertionConsumerServiceURL` def services(type) - document.find_all("/md:EntityDescriptor/md:#{name}/md:#{type}").map do |item| + search("/md:EntityDescriptor/md:#{name}/md:#{type}").map do |item| binding = item.attribute('Binding').value location = item.attribute('Location').value Saml::Kit::Bindings.create_for(binding, location) end end @@ -130,26 +130,22 @@ # @param user [Object] a user object that responds to `name_id_for` and `assertion_attributes_for`. # @param binding [Symbol] can be `:http_post` or `:http_redirect`. # @param relay_state [String] the relay state to have echo'd back. # @return [Array] Returns an array with a url and Hash of parameters to send to the other party. def logout_request_for(user, binding: :http_post, relay_state: nil) - builder = Saml::Kit::LogoutRequest.builder(user) do |x| - yield x if block_given? - end + builder = Saml::Kit::LogoutRequest.builder(user) { |x| yield x if block_given? } request_binding = single_logout_service_for(binding: binding) request_binding.serialize(builder, relay_state: relay_state) end # Returns the certificate that matches the fingerprint # # @param fingerprint [Saml::Kit::Fingerprint] the fingerprint to search for. # @param use [Symbol] the type of certificates to look at. Can be `:signing` or `:encryption`. # @return [Xml::Kit::Certificate] returns the matching `{Xml::Kit::Certificate}` def matches?(fingerprint, use: :signing) - certificates.find do |certificate| - certificate.for?(use) && certificate.fingerprint == fingerprint - end + certificates.find { |x| x.for?(use) && x.fingerprint == fingerprint } end # Returns the XML document converted to a Hash. def to_h @xml_hash ||= Hash.from_xml(to_xml) @@ -157,11 +153,11 @@ # Returns the XML document as a String. # # @param pretty [Symbol] true to return a human friendly version of the XML. def to_xml(pretty: false) - document.to_xml(pretty: pretty) + pretty ? to_nokogiri.to_xml(indent: 2) : @xml end # Returns the XML document as a [String]. def to_s to_xml @@ -187,17 +183,19 @@ # Creates a `{Saml::Kit::Metadata}` object from a raw XML [String]. # # @param content [String] the raw metadata XML. # @return [Saml::Kit::Metadata] the metadata document or subclass. def from(content) - hash = Hash.from_xml(content) - entity_descriptor = hash['EntityDescriptor'] - if entity_descriptor.key?('SPSSODescriptor') && entity_descriptor.key?('IDPSSODescriptor') + document = Nokogiri::XML(content) + return unless document.at_xpath('/md:EntityDescriptor', NAMESPACES) + sp = document.at_xpath('/md:EntityDescriptor/md:SPSSODescriptor', NAMESPACES) + idp = document.at_xpath('/md:EntityDescriptor/md:IDPSSODescriptor', NAMESPACES) + if sp && idp Saml::Kit::CompositeMetadata.new(content) - elsif entity_descriptor.keys.include?('SPSSODescriptor') + elsif sp Saml::Kit::ServiceProviderMetadata.new(content) - elsif entity_descriptor.keys.include?('IDPSSODescriptor') + elsif idp Saml::Kit::IdentityProviderMetadata.new(content) end end # @!visibility private @@ -208,19 +206,24 @@ private attr_reader :xml - def document - @document ||= ::Xml::Kit::Document.new(xml, namespaces: NAMESPACES) + # @!visibility private + def to_nokogiri + @nokogiri ||= Nokogiri::XML(xml) end def at_xpath(xpath) - document.find_by(xpath) + to_nokogiri.at_xpath(xpath, NAMESPACES) end + def search(xpath) + to_nokogiri.search(xpath, NAMESPACES) + end + def metadata - document.find_by("/md:EntityDescriptor/md:#{name}").present? + at_xpath("/md:EntityDescriptor/md:#{name}").present? end def must_contain_descriptor errors[:base] << error_message(:invalid) unless metadata end