lib/saml/kit/metadata.rb in saml-kit-0.2.14 vs lib/saml/kit/metadata.rb in saml-kit-0.2.15

- old
+ new

@@ -1,7 +1,28 @@ module Saml module Kit + # The Metadata object can be used to parse an XML string of metadata. + # + # metadata = Saml::Kit::Metadata.from(raw_xml) + # + # It can also be used to generate a new metadata string. + # + # metadata = Saml::Kit::Metadata.build do |builder| + # builder.entity_id = "my-issuer" + # builder.build_service_provider do |x| + # x.add_assertion_consumer_service(assertions_url, binding: :http_post) + # x.add_single_logout_service(logout_url, binding: :http_post) + # end + # builder.build_identity_provider do |x| + # x.add_single_sign_on_service(login_url, binding: :http_redirect) + # x.add_single_logout_service(logout_url, binding: :http_post) + # end + # end + # puts metadata.to_xml(pretty: true) + # + # See {Saml::Kit::Builders::ServiceProviderMetadata} and {Saml::Kit::Builders::IdentityProviderMetadata} + # for a list of options that can be specified. class Metadata METADATA_XSD = File.expand_path("./xsd/saml-schema-metadata-2.0.xsd", File.dirname(__FILE__)).freeze include ActiveModel::Validations include XsdValidatable include Translatable @@ -10,107 +31,150 @@ validates_presence_of :metadata validate :must_contain_descriptor validate :must_match_xsd validate :must_have_valid_signature - attr_reader :xml, :name - attr_accessor :hash_algorithm + attr_reader :name def initialize(name, xml) @name = name @xml = xml - @hash_algorithm = OpenSSL::Digest::SHA256 end + # Returns the /EntityDescriptor/@entityID def entity_id document.find_by("/md:EntityDescriptor/@entityID").value end + # Returns the supported NameIDFormats. def name_id_formats document.find_all("/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) end + # Returns the Organization URL def organization_url document.find_by("/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) 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", Xml::NAMESPACES).text Certificate.new(cert, use: item.attribute('use').value.to_sym) end end + # Returns the encryption certificates def encryption_certificates certificates.find_all(&:encryption?) end + # Returns the signing certificates. def signing_certificates certificates.find_all(&:signing?) end + # 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| binding = item.attribute("Binding").value location = item.attribute("Location").value Saml::Kit::Bindings.create_for(binding, location) end end + # Returns a specifing service binding. + # + # @param binding [Symbol] can be `:http_post` or `:http_redirect`. + # @param type [Symbol] can be on the service element like `AssertionConsumerServiceURL`, `SingleSignOnService` or `SingleLogoutService`. def service_for(binding:, type:) binding = Saml::Kit::Bindings.binding_for(binding) services(type).find { |x| x.binding?(binding) } end + # Returns each of the SingleLogoutService bindings def single_logout_services services('SingleLogoutService') end + # Returns the SingleLogoutService that matches the specified binding. + # + # @param binding [Symbol] can be `:http_post` or `:http_redirect`. def single_logout_service_for(binding:) service_for(binding: binding, type: 'SingleLogoutService') end + # Creates a serialized LogoutRequest. + # + # @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 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 [Saml::Kit::Certificate] returns the matching `{Saml::Kit::Certificate}` def matches?(fingerprint, use: :signing) certificates.find do |certificate| certificate.for?(use) && certificate.fingerprint == fingerprint end end + # Returns the XML document converted to a Hash. def to_h @xml_hash ||= Hash.from_xml(to_xml) end + # 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) end + # Returns the XML document as a [String]. def to_s to_xml end + # Verifies the signature and data using the signing certificates. + # + # @param algorithm [OpenSSL::Digest] the digest algorithm to use. E.g. `OpenSSL::Digest::SHA256` + # @param signature [String] the signature to verify + # @param data [String] the data that is used to produce the signature. + # @return [Saml::Kit::Certificate] the certificate that was used to produce the signature. def verify(algorithm, signature, data) signing_certificates.find do |certificate| certificate.public_key.verify(algorithm, signature, data) end end + # 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 self.from(content) hash = Hash.from_xml(content) entity_descriptor = hash["EntityDescriptor"] if entity_descriptor.key?("SPSSODescriptor") && entity_descriptor.key?("IDPSSODescriptor") Saml::Kit::CompositeMetadata.new(content) @@ -119,14 +183,17 @@ elsif entity_descriptor.keys.include?("IDPSSODescriptor") Saml::Kit::IdentityProviderMetadata.new(content) end end + # @!visibility private def self.builder_class Saml::Kit::Builders::Metadata end private + + attr_reader :xml def document @document ||= Xml.new(xml) end