ruby-saml 1.1.2 → 1.2.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 +7 -0
- data/README.md +15 -6
- data/changelog.md +15 -1
- data/lib/onelogin/ruby-saml/authrequest.rb +2 -2
- data/lib/onelogin/ruby-saml/error_handling.rb +27 -0
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +80 -16
- data/lib/onelogin/ruby-saml/logoutrequest.rb +3 -4
- data/lib/onelogin/ruby-saml/logoutresponse.rb +20 -22
- data/lib/onelogin/ruby-saml/metadata.rb +3 -4
- data/lib/onelogin/ruby-saml/response.rb +79 -50
- data/lib/onelogin/ruby-saml/saml_message.rb +3 -10
- data/lib/onelogin/ruby-saml/settings.rb +2 -0
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +32 -32
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +3 -3
- data/lib/onelogin/ruby-saml/utils.rb +25 -9
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +29 -27
- data/ruby-saml.gemspec +4 -1
- data/test/idp_metadata_parser_test.rb +28 -0
- data/test/logoutrequest_test.rb +2 -1
- data/test/logoutresponse_test.rb +9 -0
- data/test/metadata_test.rb +14 -0
- data/test/response_test.rb +70 -6
- data/test/responses/idp_descriptor.xml +1 -1
- data/test/responses/response_with_retrieval_method.xml +26 -0
- data/test/responses/response_without_reference_uri.xml.base64 +1 -1
- data/test/settings_test.rb +1 -1
- data/test/slo_logoutrequest_test.rb +29 -2
- data/test/test_helper.rb +1 -1
- data/test/utils_test.rb +14 -1
- data/test/xml_security_test.rb +2 -0
- metadata +133 -177
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: 162de30b9475ee4bc219f26c544304d051ab5c34
         | 
| 4 | 
            +
              data.tar.gz: 685afabca84ac713ab43a2c848cfdb20dd92f579
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: e5a3f645b2cf71452e7f22a2bbc853f393978516e1abc185b3cbbfe4aa19403b3b3760c839d72f81fd36128241e09c58dff8c84c5b1b2dc1b0177a85b235cf81
         | 
| 7 | 
            +
              data.tar.gz: 7ef80e8936bda82946041ebc220002b7d7a745819a2a419f190c6a0980449cd00516338df8879dcb67b13dd882ba155a665ed31154113fc2826112629fbbedcc
         | 
    
        data/README.md
    CHANGED
    
    | @@ -1,12 +1,17 @@ | |
| 1 1 | 
             
            # Ruby SAML [](http://travis-ci.org/onelogin/ruby-saml) [](https://coveralls.io/r/onelogin/ruby-saml?branch=master%0A) [](http://badge.fury.io/rb/ruby-saml)
         | 
| 2 2 |  | 
| 3 | 
            +
            ## Updating from 1.1.x to 1.2.X
         | 
| 3 4 |  | 
| 4 | 
            -
             | 
| 5 | 
            +
            Version `1.2` adds IDP metadata parsing improvements, uuid deprecation in favour of SecureRandom, refactor error handling and some minor improvements
         | 
| 5 6 |  | 
| 6 | 
            -
             | 
| 7 | 
            +
            There is no compatibility issue detected.
         | 
| 7 8 |  | 
| 8 9 | 
             
            For more details, please review [the changelog](changelog.md).
         | 
| 9 10 |  | 
| 11 | 
            +
            ## Updating from 1.0.x to 1.1.X
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            Version `1.1` adds some improvements on signature validation and solves some namespace conflicts.
         | 
| 14 | 
            +
             | 
| 10 15 | 
             
            ## Updating from 0.9.x to 1.0.X
         | 
| 11 16 |  | 
| 12 17 | 
             
            Version `1.0` is a recommended update for all Ruby SAML users as it includes security fixes.
         | 
| @@ -33,6 +38,7 @@ We created a demo project for Rails4 that uses the latest version of this librar | |
| 33 38 | 
             
            ### Supported versions of Ruby
         | 
| 34 39 | 
             
            * 1.8.7
         | 
| 35 40 | 
             
            * 1.9.x
         | 
| 41 | 
            +
            * 2.0.x
         | 
| 36 42 | 
             
            * 2.1.x
         | 
| 37 43 | 
             
            * 2.2.x
         | 
| 38 44 | 
             
            * JRuby 1.7.19
         | 
| @@ -254,7 +260,7 @@ end | |
| 254 260 | 
             
            The following attributes are set:
         | 
| 255 261 | 
             
              * idp_sso_target_url
         | 
| 256 262 | 
             
              * idp_slo_target_url
         | 
| 257 | 
            -
              *  | 
| 263 | 
            +
              * idp_cert_fingerprint
         | 
| 258 264 |  | 
| 259 265 | 
             
            If you are using saml:AttributeStatement to transfer metadata, like the user name, you can access all the attributes through response.attributes. It contains all the saml:AttributeStatement with its 'Name' as a indifferent key the one/more saml:AttributeValue as value. The value returned depends on the value of the
         | 
| 260 266 | 
             
            `single_value_compatibility` (when activate, only one value returned, the first one)
         | 
| @@ -386,7 +392,10 @@ The settings related to sign are stored in the `security` attribute of the setti | |
| 386 392 | 
             
            ```ruby
         | 
| 387 393 | 
             
              settings.security[:authn_requests_signed]   = true     # Enable or not signature on AuthNRequest
         | 
| 388 394 | 
             
              settings.security[:logout_requests_signed]  = true     # Enable or not signature on Logout Request
         | 
| 389 | 
            -
              settings.security[:logout_responses_signed] = true     # Enable or not  | 
| 395 | 
            +
              settings.security[:logout_responses_signed] = true     # Enable or not 
         | 
| 396 | 
            +
              signature on Logout Response
         | 
| 397 | 
            +
              settings.security[:want_assertions_signed]  = true     # Enable or not 
         | 
| 398 | 
            +
              the requirement of signed assertion
         | 
| 390 399 | 
             
              settings.security[:metadata_signed]         = true     # Enable or not signature on Metadata
         | 
| 391 400 |  | 
| 392 401 | 
             
              settings.security[:digest_method]    = XMLSecurity::Document::SHA1
         | 
| @@ -466,8 +475,8 @@ and this method process the SAML Logout Response sent by the IdP as reply of the | |
| 466 475 | 
             
            def process_logout_response
         | 
| 467 476 | 
             
              settings = Account.get_saml_settings
         | 
| 468 477 |  | 
| 469 | 
            -
              if session.has_key? : | 
| 470 | 
            -
                logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings, :matches_request_id => session[: | 
| 478 | 
            +
              if session.has_key? :transaction_id
         | 
| 479 | 
            +
                logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings, :matches_request_id => session[:transaction_id])
         | 
| 471 480 | 
             
              else
         | 
| 472 481 | 
             
                logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings)
         | 
| 473 482 | 
             
              end
         | 
    
        data/changelog.md
    CHANGED
    
    | @@ -1,6 +1,20 @@ | |
| 1 1 | 
             
            # RubySaml Changelog
         | 
| 2 2 |  | 
| 3 | 
            -
            ### 1. | 
| 3 | 
            +
            ### 1.2.0 (April 29, 2016)
         | 
| 4 | 
            +
            * [#269](https://github.com/onelogin/ruby-saml/pull/269) Refactor error handling; allow collect error messages when soft=true (normal validation stop after find first error)
         | 
| 5 | 
            +
            * [#289](https://github.com/onelogin/ruby-saml/pull/289) Remove uuid gem in favor of SecureRandom
         | 
| 6 | 
            +
            * [#297](https://github.com/onelogin/ruby-saml/pull/297) Implement EncryptedKey RetrievalMethod support
         | 
| 7 | 
            +
            * [#298](https://github.com/onelogin/ruby-saml/pull/298) IDP metadata parsing improved: binding parsing, fingerprint_algorithm support)
         | 
| 8 | 
            +
            * [#299](https://github.com/onelogin/ruby-saml/pull/299) Make 'signing' at KeyDescriptor optional
         | 
| 9 | 
            +
            * [#308](https://github.com/onelogin/ruby-saml/pull/308) Support name_id_format on SAMLResponse
         | 
| 10 | 
            +
            * [#315](https://github.com/onelogin/ruby-saml/pull/315) Support for canonicalization with comments
         | 
| 11 | 
            +
            * [#316](https://github.com/onelogin/ruby-saml/pull/316) Fix Misspelling of transation_id to transaction_id
         | 
| 12 | 
            +
            * [#321](https://github.com/onelogin/ruby-saml/pull/321) Support Attribute Names on IDPSSODescriptor parser
         | 
| 13 | 
            +
            * Changes on empty URI of Signature reference management
         | 
| 14 | 
            +
            * [#320](https://github.com/onelogin/ruby-saml/pull/320) Dont mutate document to fix lack of reference URI 
         | 
| 15 | 
            +
            * [#306](https://github.com/onelogin/ruby-saml/pull/306) Support WantAssertionsSigned
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            ### 1.1.2 (February 15, 2016)
         | 
| 4 18 | 
             
            * Improve signature validation. Add tests.
         | 
| 5 19 | 
             
             [#302](https://github.com/onelogin/ruby-saml/pull/302) Add Destination validation.
         | 
| 6 20 | 
             
            * [#292](https://github.com/onelogin/ruby-saml/pull/292) Improve the error message when validating the audience.
         | 
| @@ -1,8 +1,8 @@ | |
| 1 | 
            -
            require "uuid"
         | 
| 2 1 | 
             
            require "rexml/document"
         | 
| 3 2 |  | 
| 4 3 | 
             
            require "onelogin/ruby-saml/logging"
         | 
| 5 4 | 
             
            require "onelogin/ruby-saml/saml_message"
         | 
| 5 | 
            +
            require "onelogin/ruby-saml/utils"
         | 
| 6 6 |  | 
| 7 7 | 
             
            # Only supports SAML 2.0
         | 
| 8 8 | 
             
            module OneLogin
         | 
| @@ -20,7 +20,7 @@ module OneLogin | |
| 20 20 | 
             
                  # Asigns an ID, a random uuid.
         | 
| 21 21 | 
             
                  #
         | 
| 22 22 | 
             
                  def initialize
         | 
| 23 | 
            -
                    @uuid =  | 
| 23 | 
            +
                    @uuid = OneLogin::RubySaml::Utils.uuid
         | 
| 24 24 | 
             
                  end
         | 
| 25 25 |  | 
| 26 26 | 
             
                  # Creates the AuthNRequest string.
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            require "onelogin/ruby-saml/validation_error"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module OneLogin
         | 
| 4 | 
            +
              module RubySaml
         | 
| 5 | 
            +
                module ErrorHandling
         | 
| 6 | 
            +
                  attr_accessor :errors
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  # Append the cause to the errors array, and based on the value of soft, return false or raise
         | 
| 9 | 
            +
                  # an exception. soft_override is provided as a means of overriding the object's notion of
         | 
| 10 | 
            +
                  # soft for just this invocation.
         | 
| 11 | 
            +
                  def append_error(error_msg, soft_override = nil)
         | 
| 12 | 
            +
                    @errors << error_msg
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    unless soft_override.nil? ? soft : soft_override
         | 
| 15 | 
            +
                      raise ValidationError.new(error_msg)
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    false
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  # Reset the errors array
         | 
| 22 | 
            +
                  def reset_errors!
         | 
| 23 | 
            +
                    @errors = []
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         | 
| @@ -1,5 +1,4 @@ | |
| 1 1 | 
             
            require "base64"
         | 
| 2 | 
            -
            require "uuid"
         | 
| 3 2 | 
             
            require "zlib"
         | 
| 4 3 | 
             
            require "cgi"
         | 
| 5 4 | 
             
            require "net/http"
         | 
| @@ -16,8 +15,10 @@ module OneLogin | |
| 16 15 | 
             
                #
         | 
| 17 16 | 
             
                class IdpMetadataParser
         | 
| 18 17 |  | 
| 19 | 
            -
                  METADATA | 
| 20 | 
            -
                  DSIG | 
| 18 | 
            +
                  METADATA       = "urn:oasis:names:tc:SAML:2.0:metadata"
         | 
| 19 | 
            +
                  DSIG           = "http://www.w3.org/2000/09/xmldsig#"
         | 
| 20 | 
            +
                  NAME_FORMAT    = "urn:oasis:names:tc:SAML:2.0:attrname-format:*"
         | 
| 21 | 
            +
                  SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
         | 
| 21 22 |  | 
| 22 23 | 
             
                  attr_reader :document
         | 
| 23 24 | 
             
                  attr_reader :response
         | 
| @@ -26,26 +27,30 @@ module OneLogin | |
| 26 27 | 
             
                  # IdP values
         | 
| 27 28 | 
             
                  #
         | 
| 28 29 | 
             
                  # @param (see IdpMetadataParser#get_idp_metadata)
         | 
| 30 | 
            +
                  # @param options  [Hash]   :settings to provide the OneLogin::RubySaml::Settings object
         | 
| 29 31 | 
             
                  # @return (see IdpMetadataParser#get_idp_metadata)
         | 
| 30 32 | 
             
                  # @raise (see IdpMetadataParser#get_idp_metadata)
         | 
| 31 | 
            -
                  def parse_remote(url, validate_cert = true)
         | 
| 33 | 
            +
                  def parse_remote(url, validate_cert = true, options = {})
         | 
| 32 34 | 
             
                    idp_metadata = get_idp_metadata(url, validate_cert)
         | 
| 33 | 
            -
                    parse(idp_metadata)
         | 
| 35 | 
            +
                    parse(idp_metadata, options)
         | 
| 34 36 | 
             
                  end
         | 
| 35 37 |  | 
| 36 38 | 
             
                  # Parse the Identity Provider metadata and update the settings with the IdP values
         | 
| 37 39 | 
             
                  # @param idp_metadata [String] 
         | 
| 40 | 
            +
                  # @param options  [Hash]   :settings to provide the OneLogin::RubySaml::Settings object
         | 
| 38 41 | 
             
                  #
         | 
| 39 | 
            -
                  def parse(idp_metadata)
         | 
| 42 | 
            +
                  def parse(idp_metadata, options = {})
         | 
| 40 43 | 
             
                    @document = REXML::Document.new(idp_metadata)
         | 
| 41 44 |  | 
| 42 | 
            -
                    OneLogin::RubySaml::Settings.new.tap do |settings|
         | 
| 45 | 
            +
                    (options[:settings] || OneLogin::RubySaml::Settings.new).tap do |settings|
         | 
| 43 46 | 
             
                      settings.idp_entity_id = idp_entity_id
         | 
| 44 47 | 
             
                      settings.name_identifier_format = idp_name_id_format
         | 
| 45 | 
            -
                      settings.idp_sso_target_url = single_signon_service_url
         | 
| 46 | 
            -
                      settings.idp_slo_target_url = single_logout_service_url
         | 
| 48 | 
            +
                      settings.idp_sso_target_url = single_signon_service_url(options)
         | 
| 49 | 
            +
                      settings.idp_slo_target_url = single_logout_service_url(options)
         | 
| 47 50 | 
             
                      settings.idp_cert = certificate_base64
         | 
| 48 | 
            -
                      settings.idp_cert_fingerprint = fingerprint
         | 
| 51 | 
            +
                      settings.idp_cert_fingerprint = fingerprint(settings.idp_cert_fingerprint_algorithm)
         | 
| 52 | 
            +
                      settings.idp_attribute_names = attribute_names
         | 
| 53 | 
            +
                      settings.idp_cert_fingerprint = fingerprint(settings.idp_cert_fingerprint_algorithm)
         | 
| 49 54 | 
             
                    end
         | 
| 50 55 | 
             
                  end
         | 
| 51 56 |  | 
| @@ -112,23 +117,61 @@ module OneLogin | |
| 112 117 | 
             
                    node.text if node
         | 
| 113 118 | 
             
                  end
         | 
| 114 119 |  | 
| 120 | 
            +
                  # @param binding_priority [Array]
         | 
| 121 | 
            +
                  # @return [String|nil] SingleSignOnService binding if exists
         | 
| 122 | 
            +
                  #
         | 
| 123 | 
            +
                  def single_signon_service_binding(binding_priority = nil)
         | 
| 124 | 
            +
                    nodes = REXML::XPath.match(
         | 
| 125 | 
            +
                      document,
         | 
| 126 | 
            +
                      "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleSignOnService/@Binding",
         | 
| 127 | 
            +
                      { "md" => METADATA }
         | 
| 128 | 
            +
                    )
         | 
| 129 | 
            +
                    if binding_priority
         | 
| 130 | 
            +
                      values = nodes.map(&:value)
         | 
| 131 | 
            +
                      binding_priority.detect{ |binding| values.include? binding }
         | 
| 132 | 
            +
                    else
         | 
| 133 | 
            +
                      nodes.first.value if nodes.any?
         | 
| 134 | 
            +
                    end
         | 
| 135 | 
            +
                  end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                  # @param options [Hash]
         | 
| 115 138 | 
             
                  # @return [String|nil] SingleSignOnService endpoint if exists
         | 
| 116 139 | 
             
                  #
         | 
| 117 | 
            -
                  def single_signon_service_url
         | 
| 140 | 
            +
                  def single_signon_service_url(options = {})
         | 
| 141 | 
            +
                    binding = options[:sso_binding] || "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
         | 
| 118 142 | 
             
                    node = REXML::XPath.first(
         | 
| 119 143 | 
             
                      document,
         | 
| 120 | 
            -
                      "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleSignOnService/@Location",
         | 
| 144 | 
            +
                      "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleSignOnService[@Binding=\"#{binding}\"]/@Location",
         | 
| 121 145 | 
             
                      { "md" => METADATA }
         | 
| 122 146 | 
             
                    )
         | 
| 123 147 | 
             
                    node.value if node
         | 
| 124 148 | 
             
                  end
         | 
| 125 149 |  | 
| 150 | 
            +
                  # @param binding_priority [Array]
         | 
| 151 | 
            +
                  # @return [String|nil] SingleLogoutService binding if exists
         | 
| 152 | 
            +
                  #
         | 
| 153 | 
            +
                  def single_logout_service_binding(binding_priority = nil)
         | 
| 154 | 
            +
                    nodes = REXML::XPath.match(
         | 
| 155 | 
            +
                      document,
         | 
| 156 | 
            +
                      "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleLogoutService/@Binding",
         | 
| 157 | 
            +
                      { "md" => METADATA }
         | 
| 158 | 
            +
                    )
         | 
| 159 | 
            +
                    if binding_priority
         | 
| 160 | 
            +
                      values = nodes.map(&:value)
         | 
| 161 | 
            +
                      binding_priority.detect{ |binding| values.include? binding }
         | 
| 162 | 
            +
                    else
         | 
| 163 | 
            +
                      nodes.first.value if nodes.any?
         | 
| 164 | 
            +
                    end
         | 
| 165 | 
            +
                  end
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                  # @param options [Hash]
         | 
| 126 168 | 
             
                  # @return [String|nil] SingleLogoutService endpoint if exists
         | 
| 127 169 | 
             
                  #
         | 
| 128 | 
            -
                  def single_logout_service_url
         | 
| 170 | 
            +
                  def single_logout_service_url(options = {})
         | 
| 171 | 
            +
                    binding = options[:slo_binding] || "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
         | 
| 129 172 | 
             
                    node = REXML::XPath.first(
         | 
| 130 173 | 
             
                      document,
         | 
| 131 | 
            -
                      "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleLogoutService/@Location",
         | 
| 174 | 
            +
                      "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleLogoutService[@Binding=\"#{binding}\"]/@Location",
         | 
| 132 175 | 
             
                      { "md" => METADATA }
         | 
| 133 176 | 
             
                    )
         | 
| 134 177 | 
             
                    node.value if node
         | 
| @@ -143,6 +186,14 @@ module OneLogin | |
| 143 186 | 
             
                          "/md:EntityDescriptor/md:IDPSSODescriptor/md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
         | 
| 144 187 | 
             
                          { "md" => METADATA, "ds" => DSIG }
         | 
| 145 188 | 
             
                      )
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                      unless node
         | 
| 191 | 
            +
                        node = REXML::XPath.first(
         | 
| 192 | 
            +
                            document,
         | 
| 193 | 
            +
                            "/md:EntityDescriptor/md:IDPSSODescriptor/md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
         | 
| 194 | 
            +
                            { "md" => METADATA, "ds" => DSIG }
         | 
| 195 | 
            +
                        )
         | 
| 196 | 
            +
                      end
         | 
| 146 197 | 
             
                      node.text if node
         | 
| 147 198 | 
             
                    end
         | 
| 148 199 | 
             
                  end
         | 
| @@ -158,14 +209,27 @@ module OneLogin | |
| 158 209 |  | 
| 159 210 | 
             
                  # @return [String|nil] the SHA-1 fingerpint of the X509Certificate if it exists
         | 
| 160 211 | 
             
                  #
         | 
| 161 | 
            -
                  def fingerprint
         | 
| 212 | 
            +
                  def fingerprint(fingerprint_algorithm)
         | 
| 162 213 | 
             
                    @fingerprint ||= begin
         | 
| 163 214 | 
             
                      if certificate
         | 
| 164 215 | 
             
                        cert = OpenSSL::X509::Certificate.new(certificate)
         | 
| 165 | 
            -
             | 
| 216 | 
            +
             | 
| 217 | 
            +
                        fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(fingerprint_algorithm).new
         | 
| 218 | 
            +
                        fingerprint_alg.hexdigest(cert.to_der).upcase.scan(/../).join(":")
         | 
| 166 219 | 
             
                      end
         | 
| 167 220 | 
             
                    end
         | 
| 168 221 | 
             
                  end
         | 
| 222 | 
            +
             | 
| 223 | 
            +
                  # @return [Array] the names of all SAML attributes if any exist
         | 
| 224 | 
            +
                  #
         | 
| 225 | 
            +
                  def attribute_names
         | 
| 226 | 
            +
                    nodes = REXML::XPath.match(
         | 
| 227 | 
            +
                      document,
         | 
| 228 | 
            +
                      "/md:EntityDescriptor/md:IDPSSODescriptor/saml:Attribute/@Name",
         | 
| 229 | 
            +
                      { "md" => METADATA, "NameFormat" => NAME_FORMAT, "saml" => SAML_ASSERTION }
         | 
| 230 | 
            +
                    )
         | 
| 231 | 
            +
                    nodes.map(&:value)
         | 
| 232 | 
            +
                  end
         | 
| 169 233 | 
             
                end
         | 
| 170 234 | 
             
              end
         | 
| 171 235 | 
             
            end
         | 
| @@ -1,7 +1,6 @@ | |
| 1 | 
            -
            require "uuid"
         | 
| 2 | 
            -
             | 
| 3 1 | 
             
            require "onelogin/ruby-saml/logging"
         | 
| 4 2 | 
             
            require "onelogin/ruby-saml/saml_message"
         | 
| 3 | 
            +
            require "onelogin/ruby-saml/utils"
         | 
| 5 4 |  | 
| 6 5 | 
             
            # Only supports SAML 2.0
         | 
| 7 6 | 
             
            module OneLogin
         | 
| @@ -18,7 +17,7 @@ module OneLogin | |
| 18 17 | 
             
                  # Asigns an ID, a random uuid.
         | 
| 19 18 | 
             
                  #
         | 
| 20 19 | 
             
                  def initialize
         | 
| 21 | 
            -
                    @uuid =  | 
| 20 | 
            +
                    @uuid = OneLogin::RubySaml::Utils.uuid
         | 
| 22 21 | 
             
                  end
         | 
| 23 22 |  | 
| 24 23 | 
             
                  # Creates the Logout Request string.
         | 
| @@ -108,7 +107,7 @@ module OneLogin | |
| 108 107 | 
             
                      nameid.text = settings.name_identifier_value
         | 
| 109 108 | 
             
                    else
         | 
| 110 109 | 
             
                      # If no NameID is present in the settings we generate one
         | 
| 111 | 
            -
                      nameid.text =  | 
| 110 | 
            +
                      nameid.text = OneLogin::RubySaml::Utils.uuid
         | 
| 112 111 | 
             
                      nameid.attributes['Format'] = 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
         | 
| 113 112 | 
             
                    end
         | 
| 114 113 |  | 
| @@ -10,13 +10,11 @@ module OneLogin | |
| 10 10 | 
             
                # SAML2 Logout Response (SLO IdP initiated, Parser)
         | 
| 11 11 | 
             
                #
         | 
| 12 12 | 
             
                class Logoutresponse < SamlMessage
         | 
| 13 | 
            +
                  include ErrorHandling
         | 
| 13 14 |  | 
| 14 15 | 
             
                  # OneLogin::RubySaml::Settings Toolkit settings
         | 
| 15 16 | 
             
                  attr_accessor :settings
         | 
| 16 17 |  | 
| 17 | 
            -
                  # Array with the causes
         | 
| 18 | 
            -
                  attr_accessor :errors
         | 
| 19 | 
            -
             | 
| 20 18 | 
             
                  attr_reader :document
         | 
| 21 19 | 
             
                  attr_reader :response
         | 
| 22 20 | 
             
                  attr_reader :options
         | 
| @@ -47,18 +45,6 @@ module OneLogin | |
| 47 45 | 
             
                    @document = XMLSecurity::SignedDocument.new(@response)
         | 
| 48 46 | 
             
                  end
         | 
| 49 47 |  | 
| 50 | 
            -
                  # Append the cause to the errors array, and based on the value of soft, return false or raise
         | 
| 51 | 
            -
                  # an exception
         | 
| 52 | 
            -
                  def append_error(error_msg)
         | 
| 53 | 
            -
                    @errors << error_msg
         | 
| 54 | 
            -
                    return soft ? false : validation_error(error_msg)
         | 
| 55 | 
            -
                  end
         | 
| 56 | 
            -
             | 
| 57 | 
            -
                  # Reset the errors array
         | 
| 58 | 
            -
                  def reset_errors!
         | 
| 59 | 
            -
                    @errors = []
         | 
| 60 | 
            -
                  end
         | 
| 61 | 
            -
             | 
| 62 48 | 
             
                  # Checks if the Status has the "Success" code
         | 
| 63 49 | 
             
                  # @return [Boolean] True if the StatusCode is Sucess
         | 
| 64 50 | 
             
                  # @raise [ValidationError] if soft == false and validation fails
         | 
| @@ -117,18 +103,30 @@ module OneLogin | |
| 117 103 | 
             
                  end
         | 
| 118 104 |  | 
| 119 105 | 
             
                  # Aux function to validate the Logout Response
         | 
| 106 | 
            +
                  # @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true)
         | 
| 120 107 | 
             
                  # @return [Boolean] TRUE if the SAML Response is valid
         | 
| 121 108 | 
             
                  # @raise [ValidationError] if soft == false and validation fails
         | 
| 122 109 | 
             
                  #
         | 
| 123 | 
            -
                  def validate
         | 
| 110 | 
            +
                  def validate(collect_errors = false)
         | 
| 124 111 | 
             
                    reset_errors!
         | 
| 125 112 |  | 
| 126 | 
            -
                     | 
| 127 | 
            -
             | 
| 128 | 
            -
             | 
| 129 | 
            -
             | 
| 130 | 
            -
             | 
| 131 | 
            -
             | 
| 113 | 
            +
                    if collect_errors
         | 
| 114 | 
            +
                      valid_state?
         | 
| 115 | 
            +
                      validate_success_status
         | 
| 116 | 
            +
                      validate_structure
         | 
| 117 | 
            +
                      valid_in_response_to?
         | 
| 118 | 
            +
                      valid_issuer?
         | 
| 119 | 
            +
                      validate_signature
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                      @errors.empty?
         | 
| 122 | 
            +
                    else
         | 
| 123 | 
            +
                      valid_state? &&
         | 
| 124 | 
            +
                      validate_success_status &&
         | 
| 125 | 
            +
                      validate_structure &&
         | 
| 126 | 
            +
                      valid_in_response_to? &&
         | 
| 127 | 
            +
                      valid_issuer? &&
         | 
| 128 | 
            +
                      validate_signature
         | 
| 129 | 
            +
                    end
         | 
| 132 130 | 
             
                  end
         | 
| 133 131 |  | 
| 134 132 | 
             
                  private
         | 
| @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            require "uri"
         | 
| 2 | 
            -
            require "uuid"
         | 
| 3 2 |  | 
| 4 3 | 
             
            require "onelogin/ruby-saml/logging"
         | 
| 4 | 
            +
            require "onelogin/ruby-saml/utils"
         | 
| 5 5 |  | 
| 6 6 | 
             
            # Only supports SAML 2.0
         | 
| 7 7 | 
             
            module OneLogin
         | 
| @@ -29,8 +29,7 @@ module OneLogin | |
| 29 29 | 
             
                    sp_sso = root.add_element "md:SPSSODescriptor", {
         | 
| 30 30 | 
             
                        "protocolSupportEnumeration" => "urn:oasis:names:tc:SAML:2.0:protocol",
         | 
| 31 31 | 
             
                        "AuthnRequestsSigned" => settings.security[:authn_requests_signed],
         | 
| 32 | 
            -
                         | 
| 33 | 
            -
                        "WantAssertionsSigned" => !!(settings.idp_cert_fingerprint || settings.idp_cert)
         | 
| 32 | 
            +
                        "WantAssertionsSigned" => settings.security[:want_assertions_signed],
         | 
| 34 33 | 
             
                    }
         | 
| 35 34 |  | 
| 36 35 | 
             
                    # Add KeyDescriptor if messages will be signed / encrypted
         | 
| @@ -50,7 +49,7 @@ module OneLogin | |
| 50 49 | 
             
                      xc2.text = cert_text
         | 
| 51 50 | 
             
                    end
         | 
| 52 51 |  | 
| 53 | 
            -
                    root.attributes["ID"] =  | 
| 52 | 
            +
                    root.attributes["ID"] = OneLogin::RubySaml::Utils.uuid
         | 
| 54 53 | 
             
                    if settings.issuer
         | 
| 55 54 | 
             
                      root.attributes["entityID"] = settings.issuer
         | 
| 56 55 | 
             
                    end
         | 
| @@ -11,6 +11,8 @@ module OneLogin | |
| 11 11 | 
             
                # SAML2 Authentication Response. SAML Response
         | 
| 12 12 | 
             
                #
         | 
| 13 13 | 
             
                class Response < SamlMessage
         | 
| 14 | 
            +
                  include ErrorHandling
         | 
| 15 | 
            +
             | 
| 14 16 | 
             
                  ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
         | 
| 15 17 | 
             
                  PROTOCOL  = "urn:oasis:names:tc:SAML:2.0:protocol"
         | 
| 16 18 | 
             
                  DSIG      = "http://www.w3.org/2000/09/xmldsig#"
         | 
| @@ -21,9 +23,6 @@ module OneLogin | |
| 21 23 | 
             
                  # OneLogin::RubySaml::Settings Toolkit settings
         | 
| 22 24 | 
             
                  attr_accessor :settings
         | 
| 23 25 |  | 
| 24 | 
            -
                  # Array with the causes [Array of strings]
         | 
| 25 | 
            -
                  attr_accessor :errors
         | 
| 26 | 
            -
             | 
| 27 26 | 
             
                  attr_reader :document
         | 
| 28 27 | 
             
                  attr_reader :decrypted_document
         | 
| 29 28 | 
             
                  attr_reader :response
         | 
| @@ -39,16 +38,15 @@ module OneLogin | |
| 39 38 | 
             
                  #                          or :matches_request_id that will validate that the response matches the ID of the request,
         | 
| 40 39 | 
             
                  #                          or skip the subject confirmation validation with the :skip_subject_confirmation option
         | 
| 41 40 | 
             
                  def initialize(response, options = {})
         | 
| 42 | 
            -
                    @errors = []
         | 
| 43 | 
            -
             | 
| 44 41 | 
             
                    raise ArgumentError.new("Response cannot be nil") if response.nil?
         | 
| 45 | 
            -
                    @options = options
         | 
| 46 42 |  | 
| 43 | 
            +
                    @errors = []
         | 
| 44 | 
            +
                    @options = options
         | 
| 47 45 | 
             
                    @soft = true
         | 
| 48 | 
            -
                     | 
| 46 | 
            +
                    unless options[:settings].nil?
         | 
| 49 47 | 
             
                      @settings = options[:settings]
         | 
| 50 | 
            -
                       | 
| 51 | 
            -
                        @soft =  | 
| 48 | 
            +
                      unless @settings.soft.nil?
         | 
| 49 | 
            +
                        @soft = @settings.soft
         | 
| 52 50 | 
             
                      end
         | 
| 53 51 | 
             
                    end
         | 
| 54 52 |  | 
| @@ -60,41 +58,36 @@ module OneLogin | |
| 60 58 | 
             
                    end
         | 
| 61 59 | 
             
                  end
         | 
| 62 60 |  | 
| 63 | 
            -
                  # Append the cause to the errors array, and based on the value of soft, return false or raise
         | 
| 64 | 
            -
                  # an exception
         | 
| 65 | 
            -
                  def append_error(error_msg)
         | 
| 66 | 
            -
                    @errors << error_msg
         | 
| 67 | 
            -
                    return soft ? false : validation_error(error_msg)
         | 
| 68 | 
            -
                  end
         | 
| 69 | 
            -
             | 
| 70 | 
            -
                  # Reset the errors array
         | 
| 71 | 
            -
                  def reset_errors!
         | 
| 72 | 
            -
                    @errors = []
         | 
| 73 | 
            -
                  end
         | 
| 74 | 
            -
             | 
| 75 61 | 
             
                  # Validates the SAML Response with the default values (soft = true)
         | 
| 62 | 
            +
                  # @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true)
         | 
| 76 63 | 
             
                  # @return [Boolean] TRUE if the SAML Response is valid
         | 
| 77 64 | 
             
                  #
         | 
| 78 | 
            -
                  def is_valid?
         | 
| 79 | 
            -
                    validate
         | 
| 65 | 
            +
                  def is_valid?(collect_errors = false)
         | 
| 66 | 
            +
                    validate(collect_errors)
         | 
| 80 67 | 
             
                  end
         | 
| 81 68 |  | 
| 82 69 | 
             
                  # @return [String] the NameID provided by the SAML response from the IdP.
         | 
| 83 70 | 
             
                  #
         | 
| 84 71 | 
             
                  def name_id
         | 
| 85 | 
            -
                    @name_id ||= | 
| 86 | 
            -
                       | 
| 87 | 
            -
             | 
| 88 | 
            -
                        node = decrypt_nameid(encrypted_node)
         | 
| 89 | 
            -
                      else
         | 
| 90 | 
            -
                        node = xpath_first_from_signed_assertion('/a:Subject/a:NameID')
         | 
| 72 | 
            +
                    @name_id ||=
         | 
| 73 | 
            +
                      if name_id_node
         | 
| 74 | 
            +
                        name_id_node.text
         | 
| 91 75 | 
             
                      end
         | 
| 92 | 
            -
                      node.nil? ? nil : node.text
         | 
| 93 | 
            -
                    end
         | 
| 94 76 | 
             
                  end
         | 
| 95 77 |  | 
| 96 78 | 
             
                  alias_method :nameid, :name_id
         | 
| 97 79 |  | 
| 80 | 
            +
                  # @return [String] the NameID Format provided by the SAML response from the IdP.
         | 
| 81 | 
            +
                  #
         | 
| 82 | 
            +
                  def name_id_format
         | 
| 83 | 
            +
                    @name_id_format ||=
         | 
| 84 | 
            +
                      if name_id_node && name_id_node.attribute("Format")
         | 
| 85 | 
            +
                        name_id_node.attribute("Format").value
         | 
| 86 | 
            +
                      end
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                  alias_method :nameid_format, :name_id_format
         | 
| 90 | 
            +
             | 
| 98 91 |  | 
| 99 92 | 
             
                  # Gets the SessionIndex from the AuthnStatement.
         | 
| 100 93 | 
             
                  # Could be used to be stored in the local session in order
         | 
| @@ -291,28 +284,48 @@ module OneLogin | |
| 291 284 | 
             
                  private
         | 
| 292 285 |  | 
| 293 286 | 
             
                  # Validates the SAML Response (calls several validation methods)
         | 
| 287 | 
            +
                  # @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true)
         | 
| 294 288 | 
             
                  # @return [Boolean] True if the SAML Response is valid, otherwise False if soft=True
         | 
| 295 289 | 
             
                  # @raise [ValidationError] if soft == false and validation fails
         | 
| 296 290 | 
             
                  #
         | 
| 297 | 
            -
                  def validate
         | 
| 291 | 
            +
                  def validate(collect_errors = false)
         | 
| 298 292 | 
             
                    reset_errors!
         | 
| 299 293 |  | 
| 300 | 
            -
                     | 
| 301 | 
            -
             | 
| 302 | 
            -
             | 
| 303 | 
            -
             | 
| 304 | 
            -
             | 
| 305 | 
            -
             | 
| 306 | 
            -
             | 
| 307 | 
            -
             | 
| 308 | 
            -
             | 
| 309 | 
            -
             | 
| 310 | 
            -
             | 
| 311 | 
            -
             | 
| 312 | 
            -
             | 
| 313 | 
            -
             | 
| 314 | 
            -
             | 
| 315 | 
            -
             | 
| 294 | 
            +
                    if collect_errors
         | 
| 295 | 
            +
                      return false unless validate_response_state
         | 
| 296 | 
            +
                      validate_version
         | 
| 297 | 
            +
                      validate_id
         | 
| 298 | 
            +
                      validate_success_status
         | 
| 299 | 
            +
                      validate_num_assertion
         | 
| 300 | 
            +
                      validate_no_encrypted_attributes
         | 
| 301 | 
            +
                      validate_signed_elements
         | 
| 302 | 
            +
                      validate_structure
         | 
| 303 | 
            +
                      validate_in_response_to
         | 
| 304 | 
            +
                      validate_conditions
         | 
| 305 | 
            +
                      validate_audience
         | 
| 306 | 
            +
                      validate_issuer
         | 
| 307 | 
            +
                      validate_session_expiration
         | 
| 308 | 
            +
                      validate_subject_confirmation
         | 
| 309 | 
            +
                      validate_signature
         | 
| 310 | 
            +
                      @errors.empty?
         | 
| 311 | 
            +
                    else
         | 
| 312 | 
            +
                      validate_response_state &&
         | 
| 313 | 
            +
                      validate_version &&
         | 
| 314 | 
            +
                      validate_id &&
         | 
| 315 | 
            +
                      validate_success_status &&
         | 
| 316 | 
            +
                      validate_num_assertion &&
         | 
| 317 | 
            +
                      validate_no_encrypted_attributes &&
         | 
| 318 | 
            +
                      validate_signed_elements &&
         | 
| 319 | 
            +
                      validate_structure &&
         | 
| 320 | 
            +
                      validate_in_response_to &&
         | 
| 321 | 
            +
                      validate_conditions &&
         | 
| 322 | 
            +
                      validate_audience &&
         | 
| 323 | 
            +
                      validate_destination &&
         | 
| 324 | 
            +
                      validate_issuer &&
         | 
| 325 | 
            +
                      validate_session_expiration &&
         | 
| 326 | 
            +
                      validate_subject_confirmation &&
         | 
| 327 | 
            +
                      validate_signature
         | 
| 328 | 
            +
                    end
         | 
| 316 329 | 
             
                  end
         | 
| 317 330 |  | 
| 318 331 |  | 
| @@ -442,6 +455,10 @@ module OneLogin | |
| 442 455 | 
             
                      return append_error("Found an unexpected number of Signature Element. SAML Response rejected")
         | 
| 443 456 | 
             
                    end
         | 
| 444 457 |  | 
| 458 | 
            +
                    if settings.security[:want_assertions_signed] && !(signed_elements.include? "Assertion")
         | 
| 459 | 
            +
                      return append_error("The Assertion of the Response is not signed and the SP requires it")
         | 
| 460 | 
            +
                    end
         | 
| 461 | 
            +
             | 
| 445 462 | 
             
                    true
         | 
| 446 463 | 
             
                  end
         | 
| 447 464 |  | 
| @@ -638,6 +655,18 @@ module OneLogin | |
| 638 655 | 
             
                    true
         | 
| 639 656 | 
             
                  end
         | 
| 640 657 |  | 
| 658 | 
            +
                  def name_id_node
         | 
| 659 | 
            +
                    @name_id_node ||=
         | 
| 660 | 
            +
                      begin
         | 
| 661 | 
            +
                        encrypted_node = xpath_first_from_signed_assertion('/a:Subject/a:EncryptedID')
         | 
| 662 | 
            +
                        if encrypted_node
         | 
| 663 | 
            +
                          node = decrypt_nameid(encrypted_node)
         | 
| 664 | 
            +
                        else
         | 
| 665 | 
            +
                          node = xpath_first_from_signed_assertion('/a:Subject/a:NameID')
         | 
| 666 | 
            +
                        end
         | 
| 667 | 
            +
                      end
         | 
| 668 | 
            +
                  end
         | 
| 669 | 
            +
             | 
| 641 670 | 
             
                  # Extracts the first appearance that matchs the subelt (pattern)
         | 
| 642 671 | 
             
                  # Search on any Assertion that is signed, or has a Response parent signed
         | 
| 643 672 | 
             
                  # @param subelt [String] The XPath pattern
         | 
| @@ -686,7 +715,7 @@ module OneLogin | |
| 686 715 | 
             
                  #
         | 
| 687 716 | 
             
                  def generate_decrypted_document
         | 
| 688 717 | 
             
                    if settings.nil? || !settings.get_sp_key
         | 
| 689 | 
            -
                       | 
| 718 | 
            +
                      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')
         | 
| 690 719 | 
             
                    end
         | 
| 691 720 |  | 
| 692 721 | 
             
                    # Marshal at Ruby 1.8.7 throw an Exception
         | 
| @@ -752,7 +781,7 @@ module OneLogin | |
| 752 781 | 
             
                  #
         | 
| 753 782 | 
             
                  def decrypt_element(encrypt_node, rgrex)
         | 
| 754 783 | 
             
                    if settings.nil? || !settings.get_sp_key
         | 
| 755 | 
            -
                       | 
| 784 | 
            +
                      raise ValidationError.new('An ' + encrypt_node.name + ' found and no SP private key found on the settings to decrypt it')
         | 
| 756 785 | 
             
                    end
         | 
| 757 786 |  | 
| 758 787 | 
             
                    elem_plaintext = OneLogin::RubySaml::Utils.decrypt_data(encrypt_node, settings.get_sp_key)
         |