ruby-saml 1.13.0 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ruby-saml might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/.github/FUNDING.yml +3 -0
- data/.github/workflows/test.yml +67 -3
- data/CHANGELOG.md +31 -1
- data/LICENSE +2 -1
- data/README.md +141 -40
- data/UPGRADING.md +1 -1
- data/lib/onelogin/ruby-saml/authrequest.rb +8 -9
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +3 -3
- data/lib/onelogin/ruby-saml/logoutrequest.rb +7 -7
- data/lib/onelogin/ruby-saml/logoutresponse.rb +1 -1
- data/lib/onelogin/ruby-saml/metadata.rb +21 -25
- data/lib/onelogin/ruby-saml/response.rb +25 -20
- data/lib/onelogin/ruby-saml/saml_message.rb +2 -3
- data/lib/onelogin/ruby-saml/settings.rb +137 -42
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +39 -33
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +8 -8
- data/lib/onelogin/ruby-saml/utils.rb +96 -26
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +22 -9
- data/ruby-saml.gemspec +41 -13
- metadata +50 -40
| @@ -613,7 +613,12 @@ module OneLogin | |
| 613 613 | 
             
                  #
         | 
| 614 614 | 
             
                  def validate_audience
         | 
| 615 615 | 
             
                    return true if options[:skip_audience]
         | 
| 616 | 
            -
                    return true if  | 
| 616 | 
            +
                    return true if settings.sp_entity_id.nil? || settings.sp_entity_id.empty?
         | 
| 617 | 
            +
             | 
| 618 | 
            +
                    if audiences.empty?
         | 
| 619 | 
            +
                      return true unless settings.security[:strict_audience_validation]
         | 
| 620 | 
            +
                      return append_error("Invalid Audiences. The <AudienceRestriction> element contained only empty <Audience> elements. Expected audience #{settings.sp_entity_id}.")
         | 
| 621 | 
            +
                    end
         | 
| 617 622 |  | 
| 618 623 | 
             
                    unless audiences.include? settings.sp_entity_id
         | 
| 619 624 | 
             
                      s = audiences.count > 1 ? 's' : '';
         | 
| @@ -736,7 +741,7 @@ module OneLogin | |
| 736 741 | 
             
                  # @return [Boolean] True if the SessionNotOnOrAfter of the AuthnStatement is valid, otherwise (when expired) False if soft=True
         | 
| 737 742 | 
             
                  # @raise [ValidationError] if soft == false and validation fails
         | 
| 738 743 | 
             
                  #
         | 
| 739 | 
            -
                  def validate_session_expiration | 
| 744 | 
            +
                  def validate_session_expiration
         | 
| 740 745 | 
             
                    return true if session_expires_at.nil?
         | 
| 741 746 |  | 
| 742 747 | 
             
                    now = Time.now.utc
         | 
| @@ -910,9 +915,9 @@ module OneLogin | |
| 910 915 | 
             
                      begin
         | 
| 911 916 | 
             
                        encrypted_node = xpath_first_from_signed_assertion('/a:Subject/a:EncryptedID')
         | 
| 912 917 | 
             
                        if encrypted_node
         | 
| 913 | 
            -
                           | 
| 918 | 
            +
                          decrypt_nameid(encrypted_node)
         | 
| 914 919 | 
             
                        else
         | 
| 915 | 
            -
                           | 
| 920 | 
            +
                          xpath_first_from_signed_assertion('/a:Subject/a:NameID')
         | 
| 916 921 | 
             
                        end
         | 
| 917 922 | 
             
                      end
         | 
| 918 923 | 
             
                  end
         | 
| @@ -964,7 +969,7 @@ module OneLogin | |
| 964 969 | 
             
                  # @return [XMLSecurity::SignedDocument] The SAML Response with the assertion decrypted
         | 
| 965 970 | 
             
                  #
         | 
| 966 971 | 
             
                  def generate_decrypted_document
         | 
| 967 | 
            -
                    if settings.nil? ||  | 
| 972 | 
            +
                    if settings.nil? || settings.get_sp_decryption_keys.empty?
         | 
| 968 973 | 
             
                      raise ValidationError.new('An EncryptedAssertion found and no SP private key found on the settings to decrypt it. Be sure you provided the :settings parameter at the initialize method')
         | 
| 969 974 | 
             
                    end
         | 
| 970 975 |  | 
| @@ -1007,42 +1012,42 @@ module OneLogin | |
| 1007 1012 | 
             
                  end
         | 
| 1008 1013 |  | 
| 1009 1014 | 
             
                  # Decrypts an EncryptedID element
         | 
| 1010 | 
            -
                  # @param  | 
| 1015 | 
            +
                  # @param encrypted_id_node [REXML::Element] The EncryptedID element
         | 
| 1011 1016 | 
             
                  # @return [REXML::Document] The decrypted EncrypedtID element
         | 
| 1012 1017 | 
             
                  #
         | 
| 1013 | 
            -
                  def decrypt_nameid( | 
| 1014 | 
            -
                    decrypt_element( | 
| 1018 | 
            +
                  def decrypt_nameid(encrypted_id_node)
         | 
| 1019 | 
            +
                    decrypt_element(encrypted_id_node, /(.*<\/(\w+:)?NameID>)/m)
         | 
| 1015 1020 | 
             
                  end
         | 
| 1016 1021 |  | 
| 1017 | 
            -
                  # Decrypts an  | 
| 1018 | 
            -
                  # @param  | 
| 1019 | 
            -
                  # @return [REXML::Document] The decrypted  | 
| 1022 | 
            +
                  # Decrypts an EncryptedAttribute element
         | 
| 1023 | 
            +
                  # @param encrypted_attribute_node [REXML::Element] The EncryptedAttribute element
         | 
| 1024 | 
            +
                  # @return [REXML::Document] The decrypted EncryptedAttribute element
         | 
| 1020 1025 | 
             
                  #
         | 
| 1021 | 
            -
                  def decrypt_attribute( | 
| 1022 | 
            -
                    decrypt_element( | 
| 1026 | 
            +
                  def decrypt_attribute(encrypted_attribute_node)
         | 
| 1027 | 
            +
                    decrypt_element(encrypted_attribute_node, /(.*<\/(\w+:)?Attribute>)/m)
         | 
| 1023 1028 | 
             
                  end
         | 
| 1024 1029 |  | 
| 1025 1030 | 
             
                  # Decrypt an element
         | 
| 1026 | 
            -
                  # @param  | 
| 1027 | 
            -
                  # @param  | 
| 1031 | 
            +
                  # @param encrypt_node [REXML::Element] The encrypted element
         | 
| 1032 | 
            +
                  # @param regexp [Regexp] The regular expression to extract the decrypted data
         | 
| 1028 1033 | 
             
                  # @return [REXML::Document] The decrypted element
         | 
| 1029 1034 | 
             
                  #
         | 
| 1030 | 
            -
                  def decrypt_element(encrypt_node,  | 
| 1031 | 
            -
                    if settings.nil? ||  | 
| 1035 | 
            +
                  def decrypt_element(encrypt_node, regexp)
         | 
| 1036 | 
            +
                    if settings.nil? || settings.get_sp_decryption_keys.empty?
         | 
| 1032 1037 | 
             
                      raise ValidationError.new('An ' + encrypt_node.name + ' found and no SP private key found on the settings to decrypt it')
         | 
| 1033 1038 | 
             
                    end
         | 
| 1034 1039 |  | 
| 1035 | 
            -
             | 
| 1036 1040 | 
             
                    if encrypt_node.name == 'EncryptedAttribute'
         | 
| 1037 1041 | 
             
                      node_header = '<node xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'
         | 
| 1038 1042 | 
             
                    else
         | 
| 1039 1043 | 
             
                      node_header = '<node xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">'
         | 
| 1040 1044 | 
             
                    end
         | 
| 1041 1045 |  | 
| 1042 | 
            -
                    elem_plaintext = OneLogin::RubySaml::Utils. | 
| 1046 | 
            +
                    elem_plaintext = OneLogin::RubySaml::Utils.decrypt_multi(encrypt_node, settings.get_sp_decryption_keys)
         | 
| 1047 | 
            +
             | 
| 1043 1048 | 
             
                    # If we get some problematic noise in the plaintext after decrypting.
         | 
| 1044 1049 | 
             
                    # This quick regexp parse will grab only the Element and discard the noise.
         | 
| 1045 | 
            -
                    elem_plaintext = elem_plaintext.match( | 
| 1050 | 
            +
                    elem_plaintext = elem_plaintext.match(regexp)[0]
         | 
| 1046 1051 |  | 
| 1047 1052 | 
             
                    # To avoid namespace errors if saml namespace is not defined
         | 
| 1048 1053 | 
             
                    # create a parent node first with the namespace defined
         | 
| @@ -4,7 +4,6 @@ require 'base64' | |
| 4 4 | 
             
            require 'nokogiri'
         | 
| 5 5 | 
             
            require 'rexml/document'
         | 
| 6 6 | 
             
            require 'rexml/xpath'
         | 
| 7 | 
            -
            require 'thread'
         | 
| 8 7 | 
             
            require "onelogin/ruby-saml/error_handling"
         | 
| 9 8 |  | 
| 10 9 | 
             
            # Only supports SAML 2.0
         | 
| @@ -69,14 +68,14 @@ module OneLogin | |
| 69 68 | 
             
                      xml = Nokogiri::XML(document.to_s) do |config|
         | 
| 70 69 | 
             
                        config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
         | 
| 71 70 | 
             
                      end
         | 
| 72 | 
            -
                    rescue  | 
| 71 | 
            +
                    rescue StandardError => error
         | 
| 73 72 | 
             
                      return false if soft
         | 
| 74 73 | 
             
                      raise ValidationError.new("XML load failed: #{error.message}")
         | 
| 75 74 | 
             
                    end
         | 
| 76 75 |  | 
| 77 76 | 
             
                    SamlMessage.schema.validate(xml).map do |schema_error|
         | 
| 78 77 | 
             
                      return false if soft
         | 
| 79 | 
            -
                      raise ValidationError.new("#{schema_error.message}\n\n#{xml | 
| 78 | 
            +
                      raise ValidationError.new("#{schema_error.message}\n\n#{xml}")
         | 
| 80 79 | 
             
                    end
         | 
| 81 80 | 
             
                  end
         | 
| 82 81 |  | 
| @@ -20,7 +20,7 @@ module OneLogin | |
| 20 20 | 
             
                    end
         | 
| 21 21 |  | 
| 22 22 | 
             
                    config.each do |k,v|
         | 
| 23 | 
            -
                      acc = "#{k | 
| 23 | 
            +
                      acc = "#{k}=".to_sym
         | 
| 24 24 | 
             
                      if respond_to? acc
         | 
| 25 25 | 
             
                        value = v.is_a?(Hash) ? v.dup : v
         | 
| 26 26 | 
             
                        send(acc, value)
         | 
| @@ -60,8 +60,8 @@ module OneLogin | |
| 60 60 | 
             
                  attr_accessor :attributes_index
         | 
| 61 61 | 
             
                  attr_accessor :force_authn
         | 
| 62 62 | 
             
                  attr_accessor :certificate
         | 
| 63 | 
            -
                  attr_accessor :certificate_new
         | 
| 64 63 | 
             
                  attr_accessor :private_key
         | 
| 64 | 
            +
                  attr_accessor :sp_cert_multi
         | 
| 65 65 | 
             
                  attr_accessor :authn_context
         | 
| 66 66 | 
             
                  attr_accessor :authn_context_comparison
         | 
| 67 67 | 
             
                  attr_accessor :authn_context_decl_ref
         | 
| @@ -70,6 +70,7 @@ module OneLogin | |
| 70 70 | 
             
                  attr_accessor :security
         | 
| 71 71 | 
             
                  attr_accessor :soft
         | 
| 72 72 | 
             
                  # Deprecated
         | 
| 73 | 
            +
                  attr_accessor :certificate_new
         | 
| 73 74 | 
             
                  attr_accessor :assertion_consumer_logout_service_url
         | 
| 74 75 | 
             
                  attr_reader   :assertion_consumer_logout_service_binding
         | 
| 75 76 | 
             
                  attr_accessor :issuer
         | 
| @@ -180,10 +181,7 @@ module OneLogin | |
| 180 181 | 
             
                  # @return [OpenSSL::X509::Certificate|nil] Build the IdP certificate from the settings (previously format it)
         | 
| 181 182 | 
             
                  #
         | 
| 182 183 | 
             
                  def get_idp_cert
         | 
| 183 | 
            -
                     | 
| 184 | 
            -
             | 
| 185 | 
            -
                    formatted_cert = OneLogin::RubySaml::Utils.format_cert(idp_cert)
         | 
| 186 | 
            -
                    OpenSSL::X509::Certificate.new(formatted_cert)
         | 
| 184 | 
            +
                    OneLogin::RubySaml::Utils.build_cert_object(idp_cert)
         | 
| 187 185 | 
             
                  end
         | 
| 188 186 |  | 
| 189 187 | 
             
                  # @return [Hash with 2 arrays of OpenSSL::X509::Certificate] Build multiple IdP certificates from the settings.
         | 
| @@ -191,63 +189,79 @@ module OneLogin | |
| 191 189 | 
             
                  def get_idp_cert_multi
         | 
| 192 190 | 
             
                    return nil if idp_cert_multi.nil? || idp_cert_multi.empty?
         | 
| 193 191 |  | 
| 194 | 
            -
                    raise ArgumentError.new("Invalid value for idp_cert_multi")  | 
| 192 | 
            +
                    raise ArgumentError.new("Invalid value for idp_cert_multi") unless idp_cert_multi.is_a?(Hash)
         | 
| 195 193 |  | 
| 196 194 | 
             
                    certs = {:signing => [], :encryption => [] }
         | 
| 197 195 |  | 
| 198 | 
            -
                     | 
| 199 | 
            -
                      idp_cert_multi[ | 
| 200 | 
            -
             | 
| 201 | 
            -
                        certs[:signing].push(OpenSSL::X509::Certificate.new(formatted_cert))
         | 
| 202 | 
            -
                      end
         | 
| 203 | 
            -
                    end
         | 
| 196 | 
            +
                    [:signing, :encryption].each do |type|
         | 
| 197 | 
            +
                      certs_for_type = idp_cert_multi[type] || idp_cert_multi[type.to_s]
         | 
| 198 | 
            +
                      next if !certs_for_type || certs_for_type.empty?
         | 
| 204 199 |  | 
| 205 | 
            -
             | 
| 206 | 
            -
             | 
| 207 | 
            -
                        formatted_cert = OneLogin::RubySaml::Utils.format_cert(idp_cert)
         | 
| 208 | 
            -
                        certs[:encryption].push(OpenSSL::X509::Certificate.new(formatted_cert))
         | 
| 200 | 
            +
                      certs_for_type.each do |idp_cert|
         | 
| 201 | 
            +
                        certs[type].push(OneLogin::RubySaml::Utils.build_cert_object(idp_cert))
         | 
| 209 202 | 
             
                      end
         | 
| 210 203 | 
             
                    end
         | 
| 211 204 |  | 
| 212 205 | 
             
                    certs
         | 
| 213 206 | 
             
                  end
         | 
| 214 207 |  | 
| 215 | 
            -
                  # @return [OpenSSL::X509::Certificate | 
| 216 | 
            -
                  #
         | 
| 217 | 
            -
                   | 
| 218 | 
            -
             | 
| 208 | 
            +
                  # @return [Hash<Symbol, Array<Array<OpenSSL::X509::Certificate, OpenSSL::PKey::RSA>>>]
         | 
| 209 | 
            +
                  #   Build the SP certificates and private keys from the settings. If
         | 
| 210 | 
            +
                  #   check_sp_cert_expiration is true, only returns certificates and private keys
         | 
| 211 | 
            +
                  #   that are not expired.
         | 
| 212 | 
            +
                  def get_sp_certs
         | 
| 213 | 
            +
                    certs = get_all_sp_certs
         | 
| 214 | 
            +
                    return certs unless security[:check_sp_cert_expiration]
         | 
| 219 215 |  | 
| 220 | 
            -
                     | 
| 221 | 
            -
                     | 
| 216 | 
            +
                    active_certs = { signing: [], encryption: [] }
         | 
| 217 | 
            +
                    certs.each do |use, pairs|
         | 
| 218 | 
            +
                      next if pairs.empty?
         | 
| 222 219 |  | 
| 223 | 
            -
             | 
| 224 | 
            -
                       | 
| 225 | 
            -
             | 
| 226 | 
            -
                       | 
| 220 | 
            +
                      pairs = pairs.select { |cert, _| !cert || OneLogin::RubySaml::Utils.is_cert_active(cert) }
         | 
| 221 | 
            +
                      raise OneLogin::RubySaml::ValidationError.new("The SP certificate expired.") if pairs.empty?
         | 
| 222 | 
            +
             | 
| 223 | 
            +
                      active_certs[use] = pairs.freeze
         | 
| 227 224 | 
             
                    end
         | 
| 225 | 
            +
                    active_certs.freeze
         | 
| 226 | 
            +
                  end
         | 
| 228 227 |  | 
| 229 | 
            -
             | 
| 228 | 
            +
                  # @return [Array<OpenSSL::X509::Certificate, OpenSSL::PKey::RSA>]
         | 
| 229 | 
            +
                  #   The SP signing certificate and private key.
         | 
| 230 | 
            +
                  def get_sp_signing_pair
         | 
| 231 | 
            +
                    get_sp_certs[:signing].first
         | 
| 230 232 | 
             
                  end
         | 
| 231 233 |  | 
| 232 | 
            -
                  # @return [OpenSSL::X509::Certificate | 
| 233 | 
            -
                  #
         | 
| 234 | 
            -
                  def  | 
| 235 | 
            -
                     | 
| 234 | 
            +
                  # @return [OpenSSL::X509::Certificate] The SP signing certificate.
         | 
| 235 | 
            +
                  # @deprecated Use get_sp_signing_pair or get_sp_certs instead.
         | 
| 236 | 
            +
                  def get_sp_cert
         | 
| 237 | 
            +
                    node = get_sp_signing_pair
         | 
| 238 | 
            +
                    node[0] if node
         | 
| 239 | 
            +
                  end
         | 
| 236 240 |  | 
| 237 | 
            -
             | 
| 238 | 
            -
             | 
| 241 | 
            +
                  # @return [OpenSSL::PKey::RSA] The SP signing key.
         | 
| 242 | 
            +
                  def get_sp_signing_key
         | 
| 243 | 
            +
                    node = get_sp_signing_pair
         | 
| 244 | 
            +
                    node[1] if node
         | 
| 239 245 | 
             
                  end
         | 
| 240 246 |  | 
| 241 | 
            -
                  # @ | 
| 242 | 
            -
                   | 
| 243 | 
            -
                  def get_sp_key
         | 
| 244 | 
            -
                    return nil if private_key.nil? || private_key.empty?
         | 
| 247 | 
            +
                  # @deprecated Use get_sp_signing_key or get_sp_certs instead.
         | 
| 248 | 
            +
                  alias_method :get_sp_key, :get_sp_signing_key
         | 
| 245 249 |  | 
| 246 | 
            -
             | 
| 247 | 
            -
             | 
| 250 | 
            +
                  # @return [Array<OpenSSL::PKey::RSA>] The SP decryption keys.
         | 
| 251 | 
            +
                  def get_sp_decryption_keys
         | 
| 252 | 
            +
                    ary = get_sp_certs[:encryption].map { |pair| pair[1] }
         | 
| 253 | 
            +
                    ary.compact!
         | 
| 254 | 
            +
                    ary.uniq!(&:to_pem)
         | 
| 255 | 
            +
                    ary.freeze
         | 
| 248 256 | 
             
                  end
         | 
| 249 257 |  | 
| 250 | 
            -
                   | 
| 258 | 
            +
                  # @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings.
         | 
| 259 | 
            +
                  #
         | 
| 260 | 
            +
                  # @deprecated Use get_sp_certs instead
         | 
| 261 | 
            +
                  def get_sp_cert_new
         | 
| 262 | 
            +
                    node = get_sp_certs[:signing].last
         | 
| 263 | 
            +
                    node[0] if node
         | 
| 264 | 
            +
                  end
         | 
| 251 265 |  | 
| 252 266 | 
             
                  def idp_binding_from_embed_sign
         | 
| 253 267 | 
             
                    security[:embed_sign] ? Utils::BINDINGS[:post] : Utils::BINDINGS[:redirect]
         | 
| @@ -280,9 +294,90 @@ module OneLogin | |
| 280 294 | 
             
                      :digest_method              => XMLSecurity::Document::SHA1,
         | 
| 281 295 | 
             
                      :signature_method           => XMLSecurity::Document::RSA_SHA1,
         | 
| 282 296 | 
             
                      :check_idp_cert_expiration  => false,
         | 
| 283 | 
            -
                      :check_sp_cert_expiration   => false
         | 
| 297 | 
            +
                      :check_sp_cert_expiration   => false,
         | 
| 298 | 
            +
                      :strict_audience_validation => false,
         | 
| 299 | 
            +
                      :lowercase_url_encoding     => false
         | 
| 284 300 | 
             
                    }.freeze
         | 
| 285 301 | 
             
                  }.freeze
         | 
| 302 | 
            +
             | 
| 303 | 
            +
                  private
         | 
| 304 | 
            +
             | 
| 305 | 
            +
                  # @return [Hash<Symbol, Array<Array<OpenSSL::X509::Certificate, OpenSSL::PKey::RSA>>>]
         | 
| 306 | 
            +
                  #   Build the SP certificates and private keys from the settings. Returns all
         | 
| 307 | 
            +
                  #   certificates and private keys, even if they are expired.
         | 
| 308 | 
            +
                  def get_all_sp_certs
         | 
| 309 | 
            +
                    validate_sp_certs_params!
         | 
| 310 | 
            +
                    get_sp_certs_multi || get_sp_certs_single
         | 
| 311 | 
            +
                  end
         | 
| 312 | 
            +
             | 
| 313 | 
            +
                  # Validate certificate, certificate_new, private_key, and sp_cert_multi params.
         | 
| 314 | 
            +
                  def validate_sp_certs_params!
         | 
| 315 | 
            +
                    multi    = sp_cert_multi   && !sp_cert_multi.empty?
         | 
| 316 | 
            +
                    cert     = certificate     && !certificate.empty?
         | 
| 317 | 
            +
                    cert_new = certificate_new && !certificate_new.empty?
         | 
| 318 | 
            +
                    pk       = private_key     && !private_key.empty?
         | 
| 319 | 
            +
                    if multi && (cert || cert_new || pk)
         | 
| 320 | 
            +
                      raise ArgumentError.new("Cannot specify both sp_cert_multi and certificate, certificate_new, private_key parameters")
         | 
| 321 | 
            +
                    end
         | 
| 322 | 
            +
                  end
         | 
| 323 | 
            +
             | 
| 324 | 
            +
                  # Get certs from certificate, certificate_new, and private_key parameters.
         | 
| 325 | 
            +
                  def get_sp_certs_single
         | 
| 326 | 
            +
                    certs = { :signing => [], :encryption => [] }
         | 
| 327 | 
            +
             | 
| 328 | 
            +
                    sp_key = OneLogin::RubySaml::Utils.build_private_key_object(private_key)
         | 
| 329 | 
            +
                    cert = OneLogin::RubySaml::Utils.build_cert_object(certificate)
         | 
| 330 | 
            +
                    if cert || sp_key
         | 
| 331 | 
            +
                      ary = [cert, sp_key].freeze
         | 
| 332 | 
            +
                      certs[:signing] << ary
         | 
| 333 | 
            +
                      certs[:encryption] << ary
         | 
| 334 | 
            +
                    end
         | 
| 335 | 
            +
             | 
| 336 | 
            +
                    cert_new = OneLogin::RubySaml::Utils.build_cert_object(certificate_new)
         | 
| 337 | 
            +
                    if cert_new
         | 
| 338 | 
            +
                      ary = [cert_new, sp_key].freeze
         | 
| 339 | 
            +
                      certs[:signing] << ary
         | 
| 340 | 
            +
                      certs[:encryption] << ary
         | 
| 341 | 
            +
                    end
         | 
| 342 | 
            +
             | 
| 343 | 
            +
                    certs
         | 
| 344 | 
            +
                  end
         | 
| 345 | 
            +
             | 
| 346 | 
            +
                  # Get certs from get_sp_cert_multi parameter.
         | 
| 347 | 
            +
                  def get_sp_certs_multi
         | 
| 348 | 
            +
                    return if sp_cert_multi.nil? || sp_cert_multi.empty?
         | 
| 349 | 
            +
             | 
| 350 | 
            +
                    raise ArgumentError.new("sp_cert_multi must be a Hash") unless sp_cert_multi.is_a?(Hash)
         | 
| 351 | 
            +
             | 
| 352 | 
            +
                    certs = { :signing => [], :encryption => [] }.freeze
         | 
| 353 | 
            +
             | 
| 354 | 
            +
                    [:signing, :encryption].each do |type|
         | 
| 355 | 
            +
                      certs_for_type = sp_cert_multi[type] || sp_cert_multi[type.to_s]
         | 
| 356 | 
            +
                      next if !certs_for_type || certs_for_type.empty?
         | 
| 357 | 
            +
             | 
| 358 | 
            +
                      unless certs_for_type.is_a?(Array) && certs_for_type.all? { |cert| cert.is_a?(Hash) }
         | 
| 359 | 
            +
                        raise ArgumentError.new("sp_cert_multi :#{type} node must be an Array of Hashes")
         | 
| 360 | 
            +
                      end
         | 
| 361 | 
            +
             | 
| 362 | 
            +
                      certs_for_type.each do |pair|
         | 
| 363 | 
            +
                        cert = pair[:certificate] || pair['certificate'] || pair[:cert] || pair['cert']
         | 
| 364 | 
            +
                        key  = pair[:private_key] || pair['private_key'] || pair[:key] || pair['key']
         | 
| 365 | 
            +
             | 
| 366 | 
            +
                        unless cert && key
         | 
| 367 | 
            +
                          raise ArgumentError.new("sp_cert_multi :#{type} node Hashes must specify keys :certificate and :private_key")
         | 
| 368 | 
            +
                        end
         | 
| 369 | 
            +
             | 
| 370 | 
            +
                        certs[type] << [
         | 
| 371 | 
            +
                          OneLogin::RubySaml::Utils.build_cert_object(cert),
         | 
| 372 | 
            +
                          OneLogin::RubySaml::Utils.build_private_key_object(key)
         | 
| 373 | 
            +
                        ].freeze
         | 
| 374 | 
            +
                      end
         | 
| 375 | 
            +
                    end
         | 
| 376 | 
            +
             | 
| 377 | 
            +
                    certs.each { |_, ary| ary.freeze }
         | 
| 378 | 
            +
                    certs
         | 
| 379 | 
            +
                  end
         | 
| 286 380 | 
             
                end
         | 
| 287 381 | 
             
              end
         | 
| 288 382 | 
             
            end
         | 
| 383 | 
            +
             | 
| @@ -62,10 +62,7 @@ module OneLogin | |
| 62 62 | 
             
                  # @return [String] Gets the NameID of the Logout Request.
         | 
| 63 63 | 
             
                  #
         | 
| 64 64 | 
             
                  def name_id
         | 
| 65 | 
            -
                    @name_id ||=  | 
| 66 | 
            -
                      node = REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
         | 
| 67 | 
            -
                      Utils.element_text(node)
         | 
| 68 | 
            -
                    end
         | 
| 65 | 
            +
                    @name_id ||= Utils.element_text(name_id_node)
         | 
| 69 66 | 
             
                  end
         | 
| 70 67 |  | 
| 71 68 | 
             
                  alias_method :nameid, :name_id
         | 
| @@ -73,15 +70,49 @@ module OneLogin | |
| 73 70 | 
             
                  # @return [String] Gets the NameID Format of the Logout Request.
         | 
| 74 71 | 
             
                  #
         | 
| 75 72 | 
             
                  def name_id_format
         | 
| 76 | 
            -
                    @name_id_node ||= REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
         | 
| 77 73 | 
             
                    @name_id_format ||=
         | 
| 78 | 
            -
                      if  | 
| 79 | 
            -
                         | 
| 74 | 
            +
                      if name_id_node && name_id_node.attribute("Format")
         | 
| 75 | 
            +
                        name_id_node.attribute("Format").value
         | 
| 80 76 | 
             
                      end
         | 
| 81 77 | 
             
                  end
         | 
| 82 78 |  | 
| 83 79 | 
             
                  alias_method :nameid_format, :name_id_format
         | 
| 84 80 |  | 
| 81 | 
            +
                  def name_id_node
         | 
| 82 | 
            +
                    @name_id_node ||=
         | 
| 83 | 
            +
                      begin
         | 
| 84 | 
            +
                        encrypted_node = REXML::XPath.first(document, "/p:LogoutRequest/a:EncryptedID", { "p" => PROTOCOL, "a" => ASSERTION })
         | 
| 85 | 
            +
                        if encrypted_node
         | 
| 86 | 
            +
                          node = decrypt_nameid(encrypted_node)
         | 
| 87 | 
            +
                        else
         | 
| 88 | 
            +
                          node = REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
         | 
| 89 | 
            +
                        end
         | 
| 90 | 
            +
                      end
         | 
| 91 | 
            +
                  end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  # Decrypts an EncryptedID element
         | 
| 94 | 
            +
                  # @param encrypted_id_node [REXML::Element] The EncryptedID element
         | 
| 95 | 
            +
                  # @return [REXML::Document] The decrypted EncrypedtID element
         | 
| 96 | 
            +
                  #
         | 
| 97 | 
            +
                  def decrypt_nameid(encrypted_id_node)
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                    if settings.nil? || settings.get_sp_decryption_keys.empty?
         | 
| 100 | 
            +
                      raise ValidationError.new('An ' + encrypted_id_node.name + ' found and no SP private key found on the settings to decrypt it')
         | 
| 101 | 
            +
                    end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                    elem_plaintext = OneLogin::RubySaml::Utils.decrypt_multi(encrypted_id_node, settings.get_sp_decryption_keys)
         | 
| 104 | 
            +
                    # If we get some problematic noise in the plaintext after decrypting.
         | 
| 105 | 
            +
                    # This quick regexp parse will grab only the Element and discard the noise.
         | 
| 106 | 
            +
                    elem_plaintext = elem_plaintext.match(/(.*<\/(\w+:)?NameID>)/m)[0]
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                    # To avoid namespace errors if saml namespace is not defined
         | 
| 109 | 
            +
                    # create a parent node first with the namespace defined
         | 
| 110 | 
            +
                    node_header = '<node xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">'
         | 
| 111 | 
            +
                    elem_plaintext = node_header + elem_plaintext + '</node>'
         | 
| 112 | 
            +
                    doc = REXML::Document.new(elem_plaintext)
         | 
| 113 | 
            +
                    doc.root[0]
         | 
| 114 | 
            +
                  end
         | 
| 115 | 
            +
             | 
| 85 116 | 
             
                  # @return [String|nil] Gets the ID attribute from the Logout Request. if exists.
         | 
| 86 117 | 
             
                  #
         | 
| 87 118 | 
             
                  def id
         | 
| @@ -248,32 +279,8 @@ module OneLogin | |
| 248 279 | 
             
                    return true unless options.has_key? :get_params
         | 
| 249 280 | 
             
                    return true unless options[:get_params].has_key? 'Signature'
         | 
| 250 281 |  | 
| 251 | 
            -
                     | 
| 252 | 
            -
                    # of URI-encoded values _as sent by the IDP_:
         | 
| 253 | 
            -
                    #
         | 
| 254 | 
            -
                    # > Further, note that URL-encoding is not canonical; that is, there are multiple legal encodings for a given
         | 
| 255 | 
            -
                    # > value. The relying party MUST therefore perform the verification step using the original URL-encoded
         | 
| 256 | 
            -
                    # > values it received on the query string. It is not sufficient to re-encode the parameters after they have been
         | 
| 257 | 
            -
                    # > processed by software because the resulting encoding may not match the signer's encoding.
         | 
| 258 | 
            -
                    #
         | 
| 259 | 
            -
                    # <http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf>
         | 
| 260 | 
            -
                    #
         | 
| 261 | 
            -
                    # If we don't have the original parts (for backward compatibility) required to correctly verify the signature,
         | 
| 262 | 
            -
                    # then fabricate them by re-encoding the parsed URI parameters, and hope that we're lucky enough to use
         | 
| 263 | 
            -
                    # the exact same URI-encoding as the IDP. (This is not the case if the IDP is ADFS!)
         | 
| 264 | 
            -
                    options[:raw_get_params] ||= {}
         | 
| 265 | 
            -
                    if options[:raw_get_params]['SAMLRequest'].nil? && !options[:get_params]['SAMLRequest'].nil?
         | 
| 266 | 
            -
                      options[:raw_get_params]['SAMLRequest'] = CGI.escape(options[:get_params]['SAMLRequest'])
         | 
| 267 | 
            -
                    end
         | 
| 268 | 
            -
                    if options[:raw_get_params]['RelayState'].nil? && !options[:get_params]['RelayState'].nil?
         | 
| 269 | 
            -
                      options[:raw_get_params]['RelayState'] = CGI.escape(options[:get_params]['RelayState'])
         | 
| 270 | 
            -
                    end
         | 
| 271 | 
            -
                    if options[:raw_get_params]['SigAlg'].nil? && !options[:get_params]['SigAlg'].nil?
         | 
| 272 | 
            -
                      options[:raw_get_params]['SigAlg'] = CGI.escape(options[:get_params]['SigAlg'])
         | 
| 273 | 
            -
                    end
         | 
| 282 | 
            +
                    options[:raw_get_params] = OneLogin::RubySaml::Utils.prepare_raw_get_params(options[:raw_get_params], options[:get_params], settings.security[:lowercase_url_encoding])
         | 
| 274 283 |  | 
| 275 | 
            -
                    # If we only received the raw version of SigAlg,
         | 
| 276 | 
            -
                    # then parse it back into the decoded params hash for convenience.
         | 
| 277 284 | 
             
                    if options[:get_params]['SigAlg'].nil? && !options[:raw_get_params]['SigAlg'].nil?
         | 
| 278 285 | 
             
                      options[:get_params]['SigAlg'] = CGI.unescape(options[:raw_get_params]['SigAlg'])
         | 
| 279 286 | 
             
                    end
         | 
| @@ -335,7 +342,6 @@ module OneLogin | |
| 335 342 |  | 
| 336 343 | 
             
                    true
         | 
| 337 344 | 
             
                  end
         | 
| 338 | 
            -
             | 
| 339 345 | 
             
                end
         | 
| 340 346 | 
             
              end
         | 
| 341 347 | 
             
            end
         | 
| @@ -13,7 +13,7 @@ module OneLogin | |
| 13 13 | 
             
                class SloLogoutresponse < SamlMessage
         | 
| 14 14 |  | 
| 15 15 | 
             
                  # Logout Response ID
         | 
| 16 | 
            -
                   | 
| 16 | 
            +
                  attr_accessor :uuid
         | 
| 17 17 |  | 
| 18 18 | 
             
                  # Initializes the Logout Response. A SloLogoutresponse Object that is an extension of the SamlMessage class.
         | 
| 19 19 | 
             
                  # Asigns an ID, a random uuid.
         | 
| @@ -41,7 +41,7 @@ module OneLogin | |
| 41 41 | 
             
                    saml_response = CGI.escape(params.delete("SAMLResponse"))
         | 
| 42 42 | 
             
                    response_params = "#{params_prefix}SAMLResponse=#{saml_response}"
         | 
| 43 43 | 
             
                    params.each_pair do |key, value|
         | 
| 44 | 
            -
                      response_params << "&#{key | 
| 44 | 
            +
                      response_params << "&#{key}=#{CGI.escape(value.to_s)}"
         | 
| 45 45 | 
             
                    end
         | 
| 46 46 |  | 
| 47 47 | 
             
                    raise SettingError.new "Invalid settings, idp_slo_service_url is not set!" if url.nil? or url.empty?
         | 
| @@ -78,9 +78,10 @@ module OneLogin | |
| 78 78 | 
             
                    response = deflate(response) if settings.compress_response
         | 
| 79 79 | 
             
                    base64_response = encode(response)
         | 
| 80 80 | 
             
                    response_params = {"SAMLResponse" => base64_response}
         | 
| 81 | 
            +
                    sp_signing_key = settings.get_sp_signing_key
         | 
| 81 82 |  | 
| 82 | 
            -
                    if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_responses_signed] &&  | 
| 83 | 
            -
                      params['SigAlg'] | 
| 83 | 
            +
                    if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_responses_signed] && sp_signing_key
         | 
| 84 | 
            +
                      params['SigAlg'] = settings.security[:signature_method]
         | 
| 84 85 | 
             
                      url_string = OneLogin::RubySaml::Utils.build_query(
         | 
| 85 86 | 
             
                        :type => 'SAMLResponse',
         | 
| 86 87 | 
             
                        :data => base64_response,
         | 
| @@ -88,7 +89,7 @@ module OneLogin | |
| 88 89 | 
             
                        :sig_alg => params['SigAlg']
         | 
| 89 90 | 
             
                      )
         | 
| 90 91 | 
             
                      sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
         | 
| 91 | 
            -
                      signature =  | 
| 92 | 
            +
                      signature = sp_signing_key.sign(sign_algorithm.new, url_string)
         | 
| 92 93 | 
             
                      params['Signature'] = encode(signature)
         | 
| 93 94 | 
             
                    end
         | 
| 94 95 |  | 
| @@ -150,9 +151,8 @@ module OneLogin | |
| 150 151 |  | 
| 151 152 | 
             
                  def sign_document(document, settings)
         | 
| 152 153 | 
             
                    # embed signature
         | 
| 153 | 
            -
                     | 
| 154 | 
            -
             | 
| 155 | 
            -
                      cert = settings.get_sp_cert
         | 
| 154 | 
            +
                    cert, private_key = settings.get_sp_signing_pair
         | 
| 155 | 
            +
                    if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && private_key && cert
         | 
| 156 156 | 
             
                      document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
         | 
| 157 157 | 
             
                    end
         | 
| 158 158 |  |