ruby-saml 0.8.9 → 0.8.10
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/Gemfile +9 -1
- data/lib/onelogin/ruby-saml/authrequest.rb +82 -17
- data/lib/onelogin/ruby-saml/logoutrequest.rb +90 -18
- data/lib/onelogin/ruby-saml/settings.rb +73 -12
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +157 -0
- data/lib/onelogin/ruby-saml/utils.rb +79 -0
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/ruby-saml.rb +2 -1
- data/lib/xml_security.rb +151 -28
- data/test/certificates/ruby-saml.crt +14 -0
- data/test/certificates/ruby-saml.key +15 -0
- data/test/logoutrequest_test.rb +176 -41
- data/test/logoutresponse_test.rb +2 -1
- data/test/request_test.rb +100 -37
- data/test/response_test.rb +1 -1
- data/test/slo_logoutresponse_test.rb +226 -0
- data/test/test_helper.rb +37 -1
- metadata +10 -4
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 | 
            -
             | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 2 | 
            +
            SHA256:
         | 
| 3 | 
            +
              metadata.gz: 1a564ad5fdd002f4648b22638bf51dd7ad029ea633484289c7ce2e8759a3c5d6
         | 
| 4 | 
            +
              data.tar.gz: 15d0f1c459006f4c65fc9cb916b29b55098e68d9e3c5123080737b41b4e5e449
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 943d65750b9895285e60b24d612c1cce77bef3a262387121ccf9b7f8536ebfea27874d3a68ff095dc1a36e2bc3c53f509de07d2e4e31c02a20437e18015af561
         | 
| 7 | 
            +
              data.tar.gz: ccf1223639a43235641ba2533e61473e4ff248624571cd05177ccd1dd0611f7d8df16f405fd74af2987112a988f5fcb0f2f6c54fe3830f89d3688964780ea333
         | 
    
        data/Gemfile
    CHANGED
    
    | @@ -5,9 +5,16 @@ source 'http://rubygems.org' | |
| 5 5 |  | 
| 6 6 | 
             
            gemspec
         | 
| 7 7 |  | 
| 8 | 
            +
            if RUBY_VERSION < '1.9'
         | 
| 9 | 
            +
              gem 'nokogiri',   '~> 1.5.0'
         | 
| 10 | 
            +
            elsif RUBY_VERSION < '2.1'
         | 
| 11 | 
            +
              gem 'nokogiri',   '>= 1.5.0', '<= 1.6.8.1'
         | 
| 12 | 
            +
            else
         | 
| 13 | 
            +
              gem 'nokogiri',   '>= 1.5.0'
         | 
| 14 | 
            +
            end
         | 
| 15 | 
            +
             | 
| 8 16 | 
             
            group :test do
         | 
| 9 17 | 
             
              if RUBY_VERSION < '1.9'
         | 
| 10 | 
            -
                gem 'nokogiri',   '~> 1.5.0'
         | 
| 11 18 | 
             
                gem 'ruby-debug', '~> 0.10.4'
         | 
| 12 19 | 
             
              elsif RUBY_VERSION < '2.0'
         | 
| 13 20 | 
             
                gem 'debugger-linecache', '~> 1.2.0'
         | 
| @@ -23,5 +30,6 @@ group :test do | |
| 23 30 | 
             
              gem 'shoulda',   '~> 2.11'
         | 
| 24 31 | 
             
              gem 'systemu',   '~> 2'
         | 
| 25 32 | 
             
              gem 'test-unit', '~> 3.0.9'
         | 
| 33 | 
            +
              gem 'minitest',  '~> 5.5'
         | 
| 26 34 | 
             
              gem 'timecop',   '<= 0.6.0'
         | 
| 27 35 | 
             
            end
         | 
| @@ -1,16 +1,49 @@ | |
| 1 1 | 
             
            require "base64"
         | 
| 2 | 
            -
            require "uuid"
         | 
| 3 2 | 
             
            require "zlib"
         | 
| 4 3 | 
             
            require "cgi"
         | 
| 5 | 
            -
            require " | 
| 6 | 
            -
            require "rexml/xpath"
         | 
| 4 | 
            +
            require "onelogin/ruby-saml/utils"
         | 
| 7 5 |  | 
| 8 6 | 
             
            module OneLogin
         | 
| 9 7 | 
             
              module RubySaml
         | 
| 10 | 
            -
             | 
| 8 | 
            +
             | 
| 11 9 | 
             
                class Authrequest
         | 
| 10 | 
            +
                  # AuthNRequest ID
         | 
| 11 | 
            +
                  attr_reader :uuid
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  # Initializes the AuthNRequest. An Authrequest Object.
         | 
| 14 | 
            +
                  # Asigns an ID, a random uuid.
         | 
| 15 | 
            +
                  #
         | 
| 16 | 
            +
                  def initialize
         | 
| 17 | 
            +
                    @uuid = OneLogin::RubySaml::Utils.uuid
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 12 20 | 
             
                  def create(settings, params = {})
         | 
| 13 | 
            -
                    params =  | 
| 21 | 
            +
                    params = create_params(settings, params)
         | 
| 22 | 
            +
                    params_prefix = (settings.idp_sso_target_url =~ /\?/) ? '&' : '?'
         | 
| 23 | 
            +
                    saml_request = CGI.escape(params.delete("SAMLRequest"))
         | 
| 24 | 
            +
                    request_params = "#{params_prefix}SAMLRequest=#{saml_request}"
         | 
| 25 | 
            +
                    params.each_pair do |key, value|
         | 
| 26 | 
            +
                      request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
                    raise "Invalid settings, idp_sso_target_url is not set!" if settings.idp_sso_target_url.nil?
         | 
| 29 | 
            +
                    @login_url = settings.idp_sso_target_url + request_params
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  # Creates the Get parameters for the request.
         | 
| 33 | 
            +
                  # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
         | 
| 34 | 
            +
                  # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
         | 
| 35 | 
            +
                  # @return [Hash] Parameters
         | 
| 36 | 
            +
                  #
         | 
| 37 | 
            +
                  def create_params(settings, params={})
         | 
| 38 | 
            +
                    # The method expects :RelayState but sometimes we get 'RelayState' instead.
         | 
| 39 | 
            +
                    # Based on the HashWithIndifferentAccess value in Rails we could experience
         | 
| 40 | 
            +
                    # conflicts so this line will solve them.
         | 
| 41 | 
            +
                    relay_state = params[:RelayState] || params['RelayState']
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    if relay_state.nil?
         | 
| 44 | 
            +
                      params.delete(:RelayState)
         | 
| 45 | 
            +
                      params.delete('RelayState')
         | 
| 46 | 
            +
                    end
         | 
| 14 47 |  | 
| 15 48 | 
             
                    request_doc = create_authentication_xml_doc(settings)
         | 
| 16 49 | 
             
                    request_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
         | 
| @@ -20,28 +53,49 @@ module OneLogin | |
| 20 53 |  | 
| 21 54 | 
             
                    Logging.debug "Created AuthnRequest: #{request}"
         | 
| 22 55 |  | 
| 23 | 
            -
                    request | 
| 56 | 
            +
                    request = Zlib::Deflate.deflate(request, 9)[2..-5] if settings.compress_request
         | 
| 24 57 | 
             
                    if Base64.respond_to?('strict_encode64')
         | 
| 25 | 
            -
             | 
| 58 | 
            +
                      base64_request = Base64.strict_encode64(request)
         | 
| 26 59 | 
             
                    else
         | 
| 27 | 
            -
             | 
| 60 | 
            +
                      base64_request = Base64.encode64(request).gsub(/\n/, "")
         | 
| 61 | 
            +
                    end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    request_params = {"SAMLRequest" => base64_request}
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                    if settings.security[:authn_requests_signed] && !settings.security[:embed_sign] && settings.private_key
         | 
| 66 | 
            +
                      params['SigAlg']    = settings.security[:signature_method]
         | 
| 67 | 
            +
                      url_string = OneLogin::RubySaml::Utils.build_query(
         | 
| 68 | 
            +
                        :type => 'SAMLRequest',
         | 
| 69 | 
            +
                        :data => base64_request,
         | 
| 70 | 
            +
                        :relay_state => relay_state,
         | 
| 71 | 
            +
                        :sig_alg => params['SigAlg']
         | 
| 72 | 
            +
                      )
         | 
| 73 | 
            +
                      sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
         | 
| 74 | 
            +
                      signature = settings.get_sp_key.sign(sign_algorithm.new, url_string)
         | 
| 75 | 
            +
                      if Base64.respond_to?('strict_encode64')
         | 
| 76 | 
            +
                        params['Signature'] = Base64.strict_encode64(signature)
         | 
| 77 | 
            +
                      else
         | 
| 78 | 
            +
                        params['Signature'] = Base64.encode64(signature).gsub(/\n/, "")
         | 
| 79 | 
            +
                      end
         | 
| 28 80 | 
             
                    end
         | 
| 29 | 
            -
                    encoded_request   = CGI.escape(base64_request)
         | 
| 30 | 
            -
                    params_prefix     = (settings.idp_sso_target_url =~ /\?/) ? '&' : '?'
         | 
| 31 | 
            -
                    request_params    = "#{params_prefix}SAMLRequest=#{encoded_request}"
         | 
| 32 81 |  | 
| 33 82 | 
             
                    params.each_pair do |key, value|
         | 
| 34 | 
            -
                      request_params  | 
| 83 | 
            +
                      request_params[key] = value.to_s
         | 
| 35 84 | 
             
                    end
         | 
| 36 85 |  | 
| 37 | 
            -
                     | 
| 86 | 
            +
                    request_params
         | 
| 38 87 | 
             
                  end
         | 
| 39 88 |  | 
| 40 89 | 
             
                  def create_authentication_xml_doc(settings)
         | 
| 41 | 
            -
                     | 
| 42 | 
            -
                     | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 90 | 
            +
                    document = create_xml_document(settings)
         | 
| 91 | 
            +
                    sign_document(document, settings)
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                  def create_xml_document(settings)
         | 
| 95 | 
            +
                    time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                    request_doc = XMLSecurity::Document.new
         | 
| 98 | 
            +
                    request_doc.uuid = uuid
         | 
| 45 99 |  | 
| 46 100 | 
             
                    root = request_doc.add_element "samlp:AuthnRequest", { "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol", "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
         | 
| 47 101 | 
             
                    root.attributes['ID'] = uuid
         | 
| @@ -97,6 +151,17 @@ module OneLogin | |
| 97 151 | 
             
                    request_doc
         | 
| 98 152 | 
             
                  end
         | 
| 99 153 |  | 
| 154 | 
            +
                  def sign_document(document, settings)
         | 
| 155 | 
            +
                    # embed signature
         | 
| 156 | 
            +
                    if settings.security[:authn_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
         | 
| 157 | 
            +
                      private_key = settings.get_sp_key
         | 
| 158 | 
            +
                      cert = settings.get_sp_cert
         | 
| 159 | 
            +
                      document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
         | 
| 160 | 
            +
                    end
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                    document
         | 
| 163 | 
            +
                  end
         | 
| 164 | 
            +
             | 
| 100 165 | 
             
                end
         | 
| 101 166 | 
             
              end
         | 
| 102 167 | 
             
            end
         | 
| @@ -1,49 +1,106 @@ | |
| 1 1 | 
             
            require "base64"
         | 
| 2 | 
            -
            require "uuid"
         | 
| 3 2 | 
             
            require "zlib"
         | 
| 4 3 | 
             
            require "cgi"
         | 
| 4 | 
            +
            require 'rexml/document'
         | 
| 5 | 
            +
            require "onelogin/ruby-saml/utils"
         | 
| 5 6 |  | 
| 6 7 | 
             
            module OneLogin
         | 
| 7 8 | 
             
              module RubySaml
         | 
| 8 | 
            -
             | 
| 9 | 
            +
             | 
| 9 10 | 
             
                class Logoutrequest
         | 
| 10 11 |  | 
| 11 12 | 
             
                  attr_reader :uuid # Can be obtained if neccessary
         | 
| 12 13 |  | 
| 13 14 | 
             
                  def initialize
         | 
| 14 | 
            -
                    @uuid =  | 
| 15 | 
            +
                    @uuid = OneLogin::RubySaml::Utils.uuid
         | 
| 15 16 | 
             
                  end
         | 
| 16 17 |  | 
| 17 18 | 
             
                  def create(settings, params={})
         | 
| 18 | 
            -
                     | 
| 19 | 
            +
                    params = create_params(settings, params)
         | 
| 20 | 
            +
                    params_prefix = (settings.idp_slo_target_url =~ /\?/) ? '&' : '?'
         | 
| 21 | 
            +
                    saml_request = CGI.escape(params.delete("SAMLRequest"))
         | 
| 22 | 
            +
                    request_params = "#{params_prefix}SAMLRequest=#{saml_request}"
         | 
| 23 | 
            +
                    params.each_pair do |key, value|
         | 
| 24 | 
            +
                      request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                    @logout_url = settings.idp_slo_target_url + request_params
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  # Creates the Get parameters for the logout request.
         | 
| 30 | 
            +
                  # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
         | 
| 31 | 
            +
                  # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
         | 
| 32 | 
            +
                  # @return [Hash] Parameters
         | 
| 33 | 
            +
                  #
         | 
| 34 | 
            +
                  def create_params(settings, params={})
         | 
| 35 | 
            +
                    # The method expects :RelayState but sometimes we get 'RelayState' instead.
         | 
| 36 | 
            +
                    # Based on the HashWithIndifferentAccess value in Rails we could experience
         | 
| 37 | 
            +
                    # conflicts so this line will solve them.
         | 
| 38 | 
            +
                    relay_state = params[:RelayState] || params['RelayState']
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                    if relay_state.nil?
         | 
| 41 | 
            +
                      params.delete(:RelayState)
         | 
| 42 | 
            +
                      params.delete('RelayState')
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    request_doc = create_logout_request_xml_doc(settings)
         | 
| 46 | 
            +
                    request_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
         | 
| 47 | 
            +
             | 
| 19 48 | 
             
                    request = ""
         | 
| 20 49 | 
             
                    request_doc.write(request)
         | 
| 21 50 |  | 
| 22 | 
            -
                     | 
| 51 | 
            +
                    Logging.debug "Created SLO Logout Request: #{request}"
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    request = Zlib::Deflate.deflate(request, 9)[2..-5] if settings.compress_request
         | 
| 23 54 | 
             
                    if Base64.respond_to?('strict_encode64')
         | 
| 24 | 
            -
             | 
| 55 | 
            +
                      base64_request = Base64.strict_encode64(request)
         | 
| 25 56 | 
             
                    else
         | 
| 26 | 
            -
             | 
| 57 | 
            +
                      base64_request = Base64.encode64(request).gsub(/\n/, "")
         | 
| 27 58 | 
             
                    end
         | 
| 28 | 
            -
                     | 
| 59 | 
            +
                    request_params = {"SAMLRequest" => base64_request}
         | 
| 29 60 |  | 
| 30 | 
            -
                     | 
| 31 | 
            -
             | 
| 61 | 
            +
                    if settings.security[:logout_requests_signed] && !settings.security[:embed_sign] && settings.private_key
         | 
| 62 | 
            +
                      params['SigAlg']    = settings.security[:signature_method]
         | 
| 63 | 
            +
                      url_string = OneLogin::RubySaml::Utils.build_query(
         | 
| 64 | 
            +
                        :type => 'SAMLRequest',
         | 
| 65 | 
            +
                        :data => base64_request,
         | 
| 66 | 
            +
                        :relay_state => relay_state,
         | 
| 67 | 
            +
                        :sig_alg => params['SigAlg']
         | 
| 68 | 
            +
                      )
         | 
| 69 | 
            +
                      sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
         | 
| 70 | 
            +
                      signature = settings.get_sp_key.sign(sign_algorithm.new, url_string)
         | 
| 71 | 
            +
                      if Base64.respond_to?('strict_encode64')
         | 
| 72 | 
            +
                        params['Signature'] = Base64.strict_encode64(signature)
         | 
| 73 | 
            +
                      else
         | 
| 74 | 
            +
                        params['Signature'] = Base64.encode64(signature).gsub(/\n/, "")
         | 
| 75 | 
            +
                      end
         | 
| 76 | 
            +
                    end
         | 
| 32 77 |  | 
| 33 78 | 
             
                    params.each_pair do |key, value|
         | 
| 34 | 
            -
                      request_params  | 
| 79 | 
            +
                      request_params[key] = value.to_s
         | 
| 35 80 | 
             
                    end
         | 
| 36 81 |  | 
| 37 | 
            -
                     | 
| 82 | 
            +
                    request_params
         | 
| 38 83 | 
             
                  end
         | 
| 39 84 |  | 
| 40 | 
            -
                   | 
| 85 | 
            +
                  # Creates the SAMLRequest String.
         | 
| 86 | 
            +
                  # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
         | 
| 87 | 
            +
                  # @return [String] The SAMLRequest String.
         | 
| 88 | 
            +
                  #
         | 
| 89 | 
            +
                  def create_logout_request_xml_doc(settings)
         | 
| 90 | 
            +
                    document = create_xml_document(settings)
         | 
| 91 | 
            +
                    sign_document(document, settings)
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                  def create_xml_document(settings, request_doc=nil)
         | 
| 95 | 
            +
                    time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
         | 
| 41 96 |  | 
| 42 | 
            -
                     | 
| 97 | 
            +
                    if request_doc.nil?
         | 
| 98 | 
            +
                      request_doc = XMLSecurity::Document.new
         | 
| 99 | 
            +
                      request_doc.uuid = uuid
         | 
| 100 | 
            +
                    end
         | 
| 43 101 |  | 
| 44 | 
            -
                    request_doc = REXML::Document.new
         | 
| 45 102 | 
             
                    root = request_doc.add_element "samlp:LogoutRequest", { "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol" }
         | 
| 46 | 
            -
                    root.attributes['ID'] =  | 
| 103 | 
            +
                    root.attributes['ID'] = uuid
         | 
| 47 104 | 
             
                    root.attributes['IssueInstant'] = time
         | 
| 48 105 | 
             
                    root.attributes['Version'] = "2.0"
         | 
| 49 106 |  | 
| @@ -57,8 +114,6 @@ module OneLogin | |
| 57 114 | 
             
                      name_id.attributes['NameQualifier'] = settings.sp_name_qualifier if settings.sp_name_qualifier
         | 
| 58 115 | 
             
                      name_id.attributes['Format'] = settings.name_identifier_format if settings.name_identifier_format
         | 
| 59 116 | 
             
                      name_id.text = settings.name_identifier_value
         | 
| 60 | 
            -
                    else
         | 
| 61 | 
            -
                      raise ValidationError.new("Missing required name identifier")
         | 
| 62 117 | 
             
                    end
         | 
| 63 118 |  | 
| 64 119 | 
             
                    if settings.sessionindex
         | 
| @@ -81,6 +136,23 @@ module OneLogin | |
| 81 136 | 
             
                    end
         | 
| 82 137 | 
             
                    request_doc
         | 
| 83 138 | 
             
                  end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                  def sign_document(document, settings)
         | 
| 141 | 
            +
                    # embed signature
         | 
| 142 | 
            +
                    if settings.security[:logout_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
         | 
| 143 | 
            +
                      private_key = settings.get_sp_key
         | 
| 144 | 
            +
                      cert = settings.get_sp_cert
         | 
| 145 | 
            +
                      document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
         | 
| 146 | 
            +
                    end
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                    document
         | 
| 149 | 
            +
                  end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                  # Leave due compatibility
         | 
| 152 | 
            +
                  def create_unauth_xml_doc(settings, params)
         | 
| 153 | 
            +
                    request_doc = ReXML::Document.new
         | 
| 154 | 
            +
                    create_xml_document(settings, request_doc)
         | 
| 155 | 
            +
                  end
         | 
| 84 156 | 
             
                end
         | 
| 85 157 | 
             
              end
         | 
| 86 158 | 
             
            end
         | 
| @@ -1,27 +1,52 @@ | |
| 1 | 
            +
            require "xml_security"
         | 
| 2 | 
            +
            require "onelogin/ruby-saml/utils"
         | 
| 3 | 
            +
             | 
| 1 4 | 
             
            module OneLogin
         | 
| 2 5 | 
             
              module RubySaml
         | 
| 3 6 | 
             
                class Settings
         | 
| 4 | 
            -
                  def initialize(overrides = {})
         | 
| 5 | 
            -
                     | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 7 | 
            +
                  def initialize(overrides = {}, keep_security_attributes = false)
         | 
| 8 | 
            +
                    if keep_security_attributes
         | 
| 9 | 
            +
                       security_attributes = overrides.delete(:security) || {}
         | 
| 10 | 
            +
                       config = DEFAULTS.merge(overrides)
         | 
| 11 | 
            +
                       config[:security] = DEFAULTS[:security].merge(security_attributes)
         | 
| 12 | 
            +
                     else
         | 
| 13 | 
            +
                       config = DEFAULTS.merge(overrides)
         | 
| 14 | 
            +
                     end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                     config.each do |k,v|
         | 
| 17 | 
            +
                       acc = "#{k.to_s}=".to_sym
         | 
| 18 | 
            +
                       if respond_to? acc
         | 
| 19 | 
            +
                         value = v.is_a?(Hash) ? v.dup : v
         | 
| 20 | 
            +
                         send(acc, value)
         | 
| 21 | 
            +
                       end
         | 
| 22 | 
            +
                     end
         | 
| 10 23 | 
             
                  end
         | 
| 11 | 
            -
             | 
| 12 | 
            -
                   | 
| 13 | 
            -
                  attr_accessor : | 
| 24 | 
            +
             | 
| 25 | 
            +
                  #idp data
         | 
| 26 | 
            +
                  attr_accessor :idp_sso_target_url
         | 
| 27 | 
            +
                  attr_accessor :idp_cert_fingerprint
         | 
| 28 | 
            +
                  attr_accessor :idp_cert
         | 
| 14 29 | 
             
                  attr_accessor :idp_slo_target_url
         | 
| 30 | 
            +
                  #sp data
         | 
| 31 | 
            +
                  attr_accessor :sp_entity_id
         | 
| 32 | 
            +
                  attr_accessor :assertion_consumer_service_url
         | 
| 33 | 
            +
                  attr_accessor :authn_context
         | 
| 34 | 
            +
                  attr_accessor :sp_name_qualifier
         | 
| 35 | 
            +
                  attr_accessor :name_identifier_format
         | 
| 15 36 | 
             
                  attr_accessor :name_identifier_value
         | 
| 16 37 | 
             
                  attr_accessor :name_identifier_value_requested
         | 
| 17 38 | 
             
                  attr_accessor :sessionindex
         | 
| 18 39 | 
             
                  attr_accessor :assertion_consumer_logout_service_url
         | 
| 19 40 | 
             
                  attr_accessor :compress_request
         | 
| 41 | 
            +
                  attr_accessor :compress_response
         | 
| 20 42 | 
             
                  attr_accessor :double_quote_xml_attribute_values
         | 
| 21 43 | 
             
                  attr_accessor :force_authn
         | 
| 22 44 | 
             
                  attr_accessor :passive
         | 
| 23 45 | 
             
                  attr_accessor :protocol_binding
         | 
| 24 | 
            -
             | 
| 46 | 
            +
                  attr_accessor :certificate
         | 
| 47 | 
            +
                  attr_accessor :private_key
         | 
| 48 | 
            +
                  # Work-flow
         | 
| 49 | 
            +
                  attr_accessor :security
         | 
| 25 50 | 
             
                  # Compability
         | 
| 26 51 | 
             
                  attr_accessor :issuer
         | 
| 27 52 | 
             
                  attr_accessor :assertion_consumer_logout_service_url
         | 
| @@ -92,14 +117,50 @@ module OneLogin | |
| 92 117 | 
             
                    @single_logout_service_binding = url
         | 
| 93 118 | 
             
                  end
         | 
| 94 119 |  | 
| 120 | 
            +
                  # @return [OpenSSL::X509::Certificate|nil] Build the SP certificate from the settings (previously format it)
         | 
| 121 | 
            +
                  #
         | 
| 122 | 
            +
                  def get_sp_cert
         | 
| 123 | 
            +
                    return nil if certificate.nil? || certificate.empty?
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                    formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
         | 
| 126 | 
            +
                    OpenSSL::X509::Certificate.new(formatted_cert)
         | 
| 127 | 
            +
                  end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                  # @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings (previously format it)
         | 
| 130 | 
            +
                  #
         | 
| 131 | 
            +
                  def get_sp_cert_new
         | 
| 132 | 
            +
                    return nil if certificate_new.nil? || certificate_new.empty?
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                    formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate_new)
         | 
| 135 | 
            +
                    OpenSSL::X509::Certificate.new(formatted_cert)
         | 
| 136 | 
            +
                  end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                  # @return [OpenSSL::PKey::RSA] Build the SP private from the settings (previously format it)
         | 
| 139 | 
            +
                  #
         | 
| 140 | 
            +
                  def get_sp_key
         | 
| 141 | 
            +
                    return nil if private_key.nil? || private_key.empty?
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                    formatted_private_key = OneLogin::RubySaml::Utils.format_private_key(private_key)
         | 
| 144 | 
            +
                    OpenSSL::PKey::RSA.new(formatted_private_key)
         | 
| 145 | 
            +
                  end
         | 
| 146 | 
            +
             | 
| 95 147 | 
             
                  private
         | 
| 96 148 |  | 
| 97 149 | 
             
                  DEFAULTS = {
         | 
| 98 150 | 
             
                    :compress_request => true,
         | 
| 151 | 
            +
                    :compress_response => true,
         | 
| 99 152 | 
             
                    :double_quote_xml_attribute_values => false,
         | 
| 100 153 | 
             
                    :assertion_consumer_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze,
         | 
| 101 | 
            -
                    :single_logout_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze
         | 
| 102 | 
            -
             | 
| 154 | 
            +
                    :single_logout_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze,
         | 
| 155 | 
            +
                    :security                                  => {
         | 
| 156 | 
            +
                      :authn_requests_signed      => false,
         | 
| 157 | 
            +
                      :logout_requests_signed     => false,
         | 
| 158 | 
            +
                      :logout_responses_signed    => false,
         | 
| 159 | 
            +
                      :embed_sign                 => false,
         | 
| 160 | 
            +
                      :digest_method              => XMLSecurity::Document::SHA1,
         | 
| 161 | 
            +
                      :signature_method           => XMLSecurity::Document::RSA_SHA1
         | 
| 162 | 
            +
                    }.freeze
         | 
| 163 | 
            +
                  }.freeze
         | 
| 103 164 | 
             
                end
         | 
| 104 165 | 
             
              end
         | 
| 105 166 | 
             
            end
         | 
| @@ -0,0 +1,157 @@ | |
| 1 | 
            +
            require "base64"
         | 
| 2 | 
            +
            require "zlib"
         | 
| 3 | 
            +
            require "cgi"
         | 
| 4 | 
            +
            require "onelogin/ruby-saml/utils"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module OneLogin
         | 
| 7 | 
            +
              module RubySaml
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                # SAML2 Logout Response (SLO SP initiated)
         | 
| 10 | 
            +
                #
         | 
| 11 | 
            +
                class SloLogoutresponse
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  # Logout Response ID
         | 
| 14 | 
            +
                  attr_reader :uuid
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  # Initializes the Logout Response. A SloLogoutresponse Object.
         | 
| 17 | 
            +
                  # Asigns an ID, a random uuid.
         | 
| 18 | 
            +
                  #
         | 
| 19 | 
            +
                  def initialize
         | 
| 20 | 
            +
                    @uuid = OneLogin::RubySaml::Utils.uuid
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  # Creates the Logout Response string.
         | 
| 24 | 
            +
                  # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
         | 
| 25 | 
            +
                  # @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
         | 
| 26 | 
            +
                  # @param logout_message [String] The Message to be placed as StatusMessage in the logout response
         | 
| 27 | 
            +
                  # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
         | 
| 28 | 
            +
                  # @return [String] Logout Request string that includes the SAMLRequest
         | 
| 29 | 
            +
                  #
         | 
| 30 | 
            +
                  def create(settings, request_id = nil, logout_message = nil, params = {})
         | 
| 31 | 
            +
                    params = create_params(settings, request_id, logout_message, params)
         | 
| 32 | 
            +
                    params_prefix = (settings.idp_slo_target_url =~ /\?/) ? '&' : '?'
         | 
| 33 | 
            +
                    saml_response = CGI.escape(params.delete("SAMLResponse"))
         | 
| 34 | 
            +
                    response_params = "#{params_prefix}SAMLResponse=#{saml_response}"
         | 
| 35 | 
            +
                    params.each_pair do |key, value|
         | 
| 36 | 
            +
                      response_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    @logout_url = settings.idp_slo_target_url + response_params
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  # Creates the Get parameters for the logout response.
         | 
| 43 | 
            +
                  # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
         | 
| 44 | 
            +
                  # @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
         | 
| 45 | 
            +
                  # @param logout_message [String] The Message to be placed as StatusMessage in the logout response
         | 
| 46 | 
            +
                  # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
         | 
| 47 | 
            +
                  # @return [Hash] Parameters
         | 
| 48 | 
            +
                  #
         | 
| 49 | 
            +
                  def create_params(settings, request_id = nil, logout_message = nil, params = {})
         | 
| 50 | 
            +
                    # The method expects :RelayState but sometimes we get 'RelayState' instead.
         | 
| 51 | 
            +
                    # Based on the HashWithIndifferentAccess value in Rails we could experience
         | 
| 52 | 
            +
                    # conflicts so this line will solve them.
         | 
| 53 | 
            +
                    relay_state = params[:RelayState] || params['RelayState']
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    if relay_state.nil?
         | 
| 56 | 
            +
                      params.delete(:RelayState)
         | 
| 57 | 
            +
                      params.delete('RelayState')
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    response_doc = create_logout_response_xml_doc(settings, request_id, logout_message)
         | 
| 61 | 
            +
                    response_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    response = ""
         | 
| 64 | 
            +
                    response_doc.write(response)
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                    Logging.debug "Created SLO Logout Response: #{response}"
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                    response = Zlib::Deflate.deflate(response, 9)[2..-5] if settings.compress_response
         | 
| 69 | 
            +
                    if Base64.respond_to?('strict_encode64')
         | 
| 70 | 
            +
                      base64_response = Base64.strict_encode64(response)
         | 
| 71 | 
            +
                    else
         | 
| 72 | 
            +
                      base64_response = Base64.encode64(response).gsub(/\n/, "")
         | 
| 73 | 
            +
                    end
         | 
| 74 | 
            +
                    response_params = {"SAMLResponse" => base64_response}
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                    if settings.security[:logout_responses_signed] && !settings.security[:embed_sign] && settings.private_key
         | 
| 77 | 
            +
                      params['SigAlg']    = settings.security[:signature_method]
         | 
| 78 | 
            +
                      url_string = OneLogin::RubySaml::Utils.build_query(
         | 
| 79 | 
            +
                        :type => 'SAMLResponse',
         | 
| 80 | 
            +
                        :data => base64_response,
         | 
| 81 | 
            +
                        :relay_state => relay_state,
         | 
| 82 | 
            +
                        :sig_alg => params['SigAlg']
         | 
| 83 | 
            +
                      )
         | 
| 84 | 
            +
                      sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
         | 
| 85 | 
            +
                      signature = settings.get_sp_key.sign(sign_algorithm.new, url_string)
         | 
| 86 | 
            +
                      if Base64.respond_to?('strict_encode64')
         | 
| 87 | 
            +
                        params['Signature'] = Base64.strict_encode64(signature)
         | 
| 88 | 
            +
                      else
         | 
| 89 | 
            +
                        params['Signature'] = Base64.encode64(signature).gsub(/\n/, "")
         | 
| 90 | 
            +
                      end
         | 
| 91 | 
            +
                    end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                    params.each_pair do |key, value|
         | 
| 94 | 
            +
                      response_params[key] = value.to_s
         | 
| 95 | 
            +
                    end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                    response_params
         | 
| 98 | 
            +
                  end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  # Creates the SAMLResponse String.
         | 
| 101 | 
            +
                  # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
         | 
| 102 | 
            +
                  # @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
         | 
| 103 | 
            +
                  # @param logout_message [String] The Message to be placed as StatusMessage in the logout response
         | 
| 104 | 
            +
                  # @return [String] The SAMLResponse String.
         | 
| 105 | 
            +
                  #
         | 
| 106 | 
            +
                  def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil)
         | 
| 107 | 
            +
                    document = create_xml_document(settings, request_id, logout_message)
         | 
| 108 | 
            +
                    sign_document(document, settings)
         | 
| 109 | 
            +
                  end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  def create_xml_document(settings, request_id = nil, logout_message = nil)
         | 
| 112 | 
            +
                    time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                    response_doc = XMLSecurity::Document.new
         | 
| 115 | 
            +
                    response_doc.uuid = uuid
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                    root = response_doc.add_element 'samlp:LogoutResponse', { 'xmlns:samlp' => 'urn:oasis:names:tc:SAML:2.0:protocol', "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
         | 
| 118 | 
            +
                    root.attributes['ID'] = uuid
         | 
| 119 | 
            +
                    root.attributes['IssueInstant'] = time
         | 
| 120 | 
            +
                    root.attributes['Version'] = '2.0'
         | 
| 121 | 
            +
                    root.attributes['InResponseTo'] = request_id unless request_id.nil?
         | 
| 122 | 
            +
                    root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil?
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                    if settings.sp_entity_id != nil
         | 
| 125 | 
            +
                      issuer = root.add_element "saml:Issuer"
         | 
| 126 | 
            +
                      issuer.text = settings.sp_entity_id
         | 
| 127 | 
            +
                    end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                    # add success message
         | 
| 130 | 
            +
                    status = root.add_element 'samlp:Status'
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                    # success status code
         | 
| 133 | 
            +
                    status_code = status.add_element 'samlp:StatusCode'
         | 
| 134 | 
            +
                    status_code.attributes['Value'] = 'urn:oasis:names:tc:SAML:2.0:status:Success'
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                    # success status message
         | 
| 137 | 
            +
                    logout_message ||= 'Successfully Signed Out'
         | 
| 138 | 
            +
                    status_message = status.add_element 'samlp:StatusMessage'
         | 
| 139 | 
            +
                    status_message.text = logout_message
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                    response_doc
         | 
| 142 | 
            +
                  end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                  def sign_document(document, settings)
         | 
| 145 | 
            +
                    # embed signature
         | 
| 146 | 
            +
                    if settings.security[:logout_responses_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
         | 
| 147 | 
            +
                      private_key = settings.get_sp_key
         | 
| 148 | 
            +
                      cert = settings.get_sp_cert
         | 
| 149 | 
            +
                      document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
         | 
| 150 | 
            +
                    end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                    document
         | 
| 153 | 
            +
                  end
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                end
         | 
| 156 | 
            +
              end
         | 
| 157 | 
            +
            end
         |