ruby-saml-mod 0.1.27 → 0.1.28
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.
- checksums.yaml +4 -4
- data/lib/onelogin/saml/response.rb +72 -32
- data/lib/xml_sec.rb +46 -0
- data/ruby-saml-mod.gemspec +1 -1
- metadata +1 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 364210a5dc8d2c29558b9b0a19c258f64cdb1c9b
         | 
| 4 | 
            +
              data.tar.gz: 5c389976863d3d599ea7fdcf1c91932f0418b169
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: b025fe482372d414d6fc263f5bbc2623e7070daa3d5fe8e2f6004b9e51494260f0746339d71791e7911c10be75b5f692069f2a98dc3d8e99985bf99c39cdab63
         | 
| 7 | 
            +
              data.tar.gz: 5826d046b3eccb253f38611afde71d6e602778b1e998d3baf3c7ba1629505821b60bfc4687f8f9e5e63164a305b429adfd39b6ccbd2358450636161eb6a73cdd
         | 
| @@ -2,7 +2,7 @@ module Onelogin::Saml | |
| 2 2 | 
             
              class Response
         | 
| 3 3 |  | 
| 4 4 | 
             
                attr_accessor :settings
         | 
| 5 | 
            -
                attr_reader :document, : | 
| 5 | 
            +
                attr_reader :document, :xml, :response
         | 
| 6 6 | 
             
                attr_reader :name_id, :name_qualifier, :session_index, :saml_attributes
         | 
| 7 7 | 
             
                attr_reader :status_code, :status_message
         | 
| 8 8 | 
             
                attr_reader :in_response_to, :destination, :issuer
         | 
| @@ -14,14 +14,14 @@ module Onelogin::Saml | |
| 14 14 | 
             
                    @xml = Base64.decode64(@response)
         | 
| 15 15 | 
             
                    @document = LibXML::XML::Document.string(@xml)
         | 
| 16 16 | 
             
                    @document.extend(XMLSecurity::SignedDocument)
         | 
| 17 | 
            -
                  rescue | 
| 17 | 
            +
                  rescue
         | 
| 18 18 | 
             
                    # could not parse document, everything is invalid
         | 
| 19 19 | 
             
                    @response = nil
         | 
| 20 20 | 
             
                    return
         | 
| 21 21 | 
             
                  end
         | 
| 22 22 |  | 
| 23 | 
            -
                  @issuer =  | 
| 24 | 
            -
                  @status_code =  | 
| 23 | 
            +
                  @issuer = document.find_first("/samlp:Response/saml:Issuer", Onelogin::NAMESPACES).content rescue nil
         | 
| 24 | 
            +
                  @status_code = document.find_first("/samlp:Response/samlp:Status/samlp:StatusCode", Onelogin::NAMESPACES)["Value"] rescue nil
         | 
| 25 25 |  | 
| 26 26 | 
             
                  process(settings) if settings
         | 
| 27 27 | 
             
                end
         | 
| @@ -30,79 +30,119 @@ module Onelogin::Saml | |
| 30 30 | 
             
                  @settings = settings
         | 
| 31 31 | 
             
                  return unless @response
         | 
| 32 32 |  | 
| 33 | 
            -
                  @ | 
| 34 | 
            -
                  @ | 
| 35 | 
            -
                  @ | 
| 33 | 
            +
                  @in_response_to = untrusted_find_first("/samlp:Response")['InResponseTo'] rescue nil
         | 
| 34 | 
            +
                  @destination    = untrusted_find_first("/samlp:Response")['Destination'] rescue nil
         | 
| 35 | 
            +
                  @status_message = untrusted_find_first("/samlp:Response/samlp:Status/samlp:StatusCode").content rescue nil
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  @name_id        = trusted_find_first("saml:Assertion/saml:Subject/saml:NameID").content rescue nil
         | 
| 38 | 
            +
                  @name_qualifier = trusted_find_first("saml:Assertion/saml:Subject/saml:NameID")["NameQualifier"] rescue nil
         | 
| 39 | 
            +
                  @session_index  = trusted_find_first("saml:Assertion/saml:AuthnStatement")["SessionIndex"] rescue nil
         | 
| 36 40 |  | 
| 37 | 
            -
                  @in_response_to = @decrypted_document.find_first("/samlp:Response", Onelogin::NAMESPACES)['InResponseTo'] rescue nil
         | 
| 38 | 
            -
                  @destination = @decrypted_document.find_first("/samlp:Response", Onelogin::NAMESPACES)['Destination'] rescue nil
         | 
| 39 | 
            -
                  @name_id = @decrypted_document.find_first("/samlp:Response/saml:Assertion/saml:Subject/saml:NameID", Onelogin::NAMESPACES).content rescue nil
         | 
| 40 41 | 
             
                  @saml_attributes = {}
         | 
| 41 | 
            -
                   | 
| 42 | 
            +
                  trusted_find("saml:Attribute").each do |attr|
         | 
| 42 43 | 
             
                    attrname = attr['FriendlyName'] || Onelogin::ATTRIBUTES[attr['Name']] || attr['Name']
         | 
| 43 44 | 
             
                    @saml_attributes[attrname] = attr.content.strip rescue nil
         | 
| 44 45 | 
             
                  end
         | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def disable_signature_validation!(settings)
         | 
| 49 | 
            +
                  @settings      = settings
         | 
| 50 | 
            +
                  @is_valid      = true
         | 
| 51 | 
            +
                  @trusted_roots = [decrypted_document.root]
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                def decrypted_document
         | 
| 55 | 
            +
                  @decrypted_document ||= LibXML::XML::Document.document(document).tap do |doc|
         | 
| 56 | 
            +
                    doc.extend(XMLSecurity::SignedDocument)
         | 
| 57 | 
            +
                    doc.decrypt!(settings)
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def untrusted_find_first(xpath)
         | 
| 62 | 
            +
                  decrypted_document.find(xpath, Onelogin::NAMESPACES).first
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                def trusted_find_first(xpath)
         | 
| 66 | 
            +
                  trusted_find(xpath).first
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                def trusted_find(xpath)
         | 
| 70 | 
            +
                  trusted_roots.map do |trusted_root|
         | 
| 71 | 
            +
                    trusted_root.find("descendant-or-self::#{xpath}", Onelogin::NAMESPACES).to_a
         | 
| 72 | 
            +
                  end.flatten.compact
         | 
| 48 73 | 
             
                end
         | 
| 49 74 |  | 
| 50 75 | 
             
                def logger=(val)
         | 
| 51 76 | 
             
                  @logger = val
         | 
| 52 77 | 
             
                end
         | 
| 53 | 
            -
             | 
| 78 | 
            +
             | 
| 54 79 | 
             
                def is_valid?
         | 
| 55 | 
            -
                   | 
| 80 | 
            +
                  @is_valid ||= validate
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                def validate
         | 
| 84 | 
            +
                  if response.nil? || response == ""
         | 
| 56 85 | 
             
                    @validation_error = "No response to validate"
         | 
| 57 86 | 
             
                    return false
         | 
| 58 87 | 
             
                  end
         | 
| 59 | 
            -
             | 
| 60 | 
            -
                  if  | 
| 88 | 
            +
             | 
| 89 | 
            +
                  if !settings.idp_cert_fingerprint
         | 
| 61 90 | 
             
                    @validation_error = "No fingerprint configured in SAML settings"
         | 
| 62 91 | 
             
                    return false
         | 
| 63 92 | 
             
                  end
         | 
| 64 | 
            -
             | 
| 93 | 
            +
             | 
| 65 94 | 
             
                  # Verify the original document if it has a signature, otherwise verify the signature
         | 
| 66 95 | 
             
                  # in the encrypted portion. If there is no signature, then we can't verify.
         | 
| 67 96 | 
             
                  verified = false
         | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 97 | 
            +
             | 
| 98 | 
            +
                  if document.has_signature?
         | 
| 99 | 
            +
                    verified = document.validate(settings.idp_cert_fingerprint, @logger)
         | 
| 70 100 | 
             
                    if !verified
         | 
| 71 | 
            -
                      @validation_error =  | 
| 101 | 
            +
                      @validation_error = document.validation_error
         | 
| 72 102 | 
             
                      return false
         | 
| 73 103 | 
             
                    end
         | 
| 74 104 | 
             
                  end
         | 
| 75 105 |  | 
| 76 | 
            -
                  if !verified &&  | 
| 77 | 
            -
                    verified =  | 
| 106 | 
            +
                  if !verified && decrypted_document.has_signature?
         | 
| 107 | 
            +
                    verified = decrypted_document.validate(settings.idp_cert_fingerprint, @logger)
         | 
| 78 108 | 
             
                    if !verified
         | 
| 79 | 
            -
                      @validation_error =  | 
| 109 | 
            +
                      @validation_error = decrypted_document.validation_error
         | 
| 80 110 | 
             
                      return false
         | 
| 81 111 | 
             
                    end
         | 
| 82 112 | 
             
                  end
         | 
| 83 | 
            -
             | 
| 113 | 
            +
             | 
| 84 114 | 
             
                  if !verified
         | 
| 85 115 | 
             
                    @validation_error = "No signature found in the response"
         | 
| 86 116 | 
             
                    return false
         | 
| 87 117 | 
             
                  end
         | 
| 88 | 
            -
             | 
| 118 | 
            +
             | 
| 119 | 
            +
                  # If we get here, validation has succeeded, and we can trust all
         | 
| 120 | 
            +
                  # <ds:Signature> elements. Each of those has a <ds:Reference> which
         | 
| 121 | 
            +
                  # points to the root of the root of the NodeSet it signs.
         | 
| 122 | 
            +
                  @trusted_roots = decrypted_document.signed_roots
         | 
| 123 | 
            +
             | 
| 89 124 | 
             
                  true
         | 
| 90 125 | 
             
                end
         | 
| 91 | 
            -
             | 
| 126 | 
            +
             | 
| 127 | 
            +
                # triggers validation
         | 
| 128 | 
            +
                def trusted_roots
         | 
| 129 | 
            +
                  is_valid? ? @trusted_roots : []
         | 
| 130 | 
            +
                end
         | 
| 131 | 
            +
             | 
| 92 132 | 
             
                def success_status?
         | 
| 93 133 | 
             
                  @status_code == Onelogin::Saml::StatusCodes::SUCCESS_URI
         | 
| 94 134 | 
             
                end
         | 
| 95 | 
            -
             | 
| 135 | 
            +
             | 
| 96 136 | 
             
                def auth_failure?
         | 
| 97 137 | 
             
                  @status_code == Onelogin::Saml::StatusCodes::AUTHN_FAILED_URI
         | 
| 98 138 | 
             
                end
         | 
| 99 | 
            -
             | 
| 139 | 
            +
             | 
| 100 140 | 
             
                def no_authn_context?
         | 
| 101 141 | 
             
                  @status_code == Onelogin::Saml::StatusCodes::NO_AUTHN_CONTEXT_URI
         | 
| 102 142 | 
             
                end
         | 
| 103 | 
            -
             | 
| 143 | 
            +
             | 
| 104 144 | 
             
                def fingerprint_from_idp
         | 
| 105 | 
            -
                  if base64_cert =  | 
| 145 | 
            +
                  if base64_cert = decrypted_document.find_first("//ds:X509Certificate", Onelogin::NAMESPACES)
         | 
| 106 146 | 
             
                    cert_text = Base64.decode64(base64_cert.content)
         | 
| 107 147 | 
             
                    cert = OpenSSL::X509::Certificate.new(cert_text)
         | 
| 108 148 | 
             
                    Digest::SHA1.hexdigest(cert.to_der)
         | 
    
        data/lib/xml_sec.rb
    CHANGED
    
    | @@ -79,6 +79,9 @@ module XMLSecurity | |
| 79 79 | 
             
                  :xmlSecDSigStatusInvalid
         | 
| 80 80 | 
             
              ]
         | 
| 81 81 |  | 
| 82 | 
            +
              XMLSEC_ERRORS_R_INVALID_DATA = 12
         | 
| 83 | 
            +
              class XmlSecError < ::RuntimeError; end
         | 
| 84 | 
            +
             | 
| 82 85 | 
             
              class XmlSecPtrList < FFI::Struct
         | 
| 83 86 | 
             
                layout \
         | 
| 84 87 | 
             
                  :id,                          :string,
         | 
| @@ -170,6 +173,12 @@ module XMLSecurity | |
| 170 173 | 
             
                  :reserved1,                   :pointer
         | 
| 171 174 | 
             
              end
         | 
| 172 175 |  | 
| 176 | 
            +
              ErrorCallback = FFI::Function.new(:void,
         | 
| 177 | 
            +
                  [ :string, :int, :string, :string,     :string,      :int,   :string ]
         | 
| 178 | 
            +
              ) do |file,    line, func,    errorObject, errorSubject, reason, msg     |
         | 
| 179 | 
            +
                XMLSecurity.handle_xmlsec_error_callback(file, line, func, errorObject, errorSubject, reason, msg)
         | 
| 180 | 
            +
              end
         | 
| 181 | 
            +
             | 
| 173 182 | 
             
              # xmlsec functions
         | 
| 174 183 | 
             
              attach_function :xmlSecInit, [], :int
         | 
| 175 184 | 
             
              attach_function :xmlSecParseMemory, [ :pointer, :uint, :int ], :pointer
         | 
| @@ -193,7 +202,9 @@ module XMLSecurity | |
| 193 202 | 
             
              attach_function :xmlSecEncCtxDecrypt, [ :pointer, :pointer ], :int
         | 
| 194 203 | 
             
              attach_function :xmlSecEncCtxDestroy, [ :pointer ], :void
         | 
| 195 204 |  | 
| 205 | 
            +
              attach_function :xmlSecErrorsDefaultCallback, [ :string, :int, :string, :string, :string, :int, :string ], :void
         | 
| 196 206 | 
             
              attach_function :xmlSecErrorsDefaultCallbackEnableOutput, [ :bool ], :void
         | 
| 207 | 
            +
              attach_function :xmlSecErrorsSetCallback, [:pointer], :void
         | 
| 197 208 |  | 
| 198 209 | 
             
              attach_function :xmlSecTransformExclC14NGetKlass, [], :pointer
         | 
| 199 210 | 
             
              attach_function :xmlSecOpenSSLTransformRsaSha1GetKlass, [], :pointer
         | 
| @@ -238,6 +249,18 @@ module XMLSecurity | |
| 238 249 | 
             
              raise "Failed initializing XMLSec" if self.xmlSecInit < 0
         | 
| 239 250 | 
             
              raise "Failed initializing app crypto" if self.xmlSecOpenSSLAppInit(nil) < 0
         | 
| 240 251 | 
             
              raise "Failed initializing crypto" if self.xmlSecOpenSSLInit < 0
         | 
| 252 | 
            +
              self.xmlSecErrorsSetCallback(ErrorCallback)
         | 
| 253 | 
            +
             | 
| 254 | 
            +
              def self.handle_xmlsec_error_callback(*args)
         | 
| 255 | 
            +
                raise_exception_if_necessary(*args)
         | 
| 256 | 
            +
                xmlSecErrorsDefaultCallback(*args)
         | 
| 257 | 
            +
              end
         | 
| 258 | 
            +
             | 
| 259 | 
            +
              def self.raise_exception_if_necessary(file, line, func, errorObject, errorSubject, reason, msg)
         | 
| 260 | 
            +
                if reason == XMLSEC_ERRORS_R_INVALID_DATA
         | 
| 261 | 
            +
                  raise XmlSecError.new(msg)
         | 
| 262 | 
            +
                end
         | 
| 263 | 
            +
              end
         | 
| 241 264 |  | 
| 242 265 |  | 
| 243 266 | 
             
              def self.mute(&block)
         | 
| @@ -267,6 +290,29 @@ module XMLSecurity | |
| 267 290 | 
             
                  "-----BEGIN PUBLIC KEY-----\n#{base64}-----END PUBLIC KEY-----"
         | 
| 268 291 | 
             
                end
         | 
| 269 292 |  | 
| 293 | 
            +
                def has_signature?
         | 
| 294 | 
            +
                  signatures.any?
         | 
| 295 | 
            +
                end
         | 
| 296 | 
            +
             | 
| 297 | 
            +
                def signed_roots
         | 
| 298 | 
            +
                  signatures.map do |sig|
         | 
| 299 | 
            +
                    ref = sig.find('.//ds:Reference', Onelogin::NAMESPACES).first
         | 
| 300 | 
            +
                    signed_element_id = ref['URI'].sub(/^#/, '')
         | 
| 301 | 
            +
             | 
| 302 | 
            +
                    if signed_element_id.empty?
         | 
| 303 | 
            +
                      self.root
         | 
| 304 | 
            +
                    else
         | 
| 305 | 
            +
                      xpath_id_query = %Q(ancestor::*[@ID = "#{signed_element_id}"])
         | 
| 306 | 
            +
             | 
| 307 | 
            +
                      ref.find(xpath_id_query, Onelogin::NAMESPACES).first
         | 
| 308 | 
            +
                    end
         | 
| 309 | 
            +
                  end.compact
         | 
| 310 | 
            +
                end
         | 
| 311 | 
            +
             | 
| 312 | 
            +
                def signatures
         | 
| 313 | 
            +
                  @signatures ||= self.find("//ds:Signature", Onelogin::NAMESPACES)
         | 
| 314 | 
            +
                end
         | 
| 315 | 
            +
             | 
| 270 316 | 
             
                def validate(idp_cert_fingerprint, logger = nil)
         | 
| 271 317 | 
             
                  # get cert from response
         | 
| 272 318 | 
             
                  base64_cert = self.find_first("//ds:X509Certificate", Onelogin::NAMESPACES).content
         | 
    
        data/ruby-saml-mod.gemspec
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: ruby-saml-mod
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.1. | 
| 4 | 
            +
              version: 0.1.28
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - OneLogin LLC
         | 
| @@ -89,4 +89,3 @@ signing_key: | |
| 89 89 | 
             
            specification_version: 4
         | 
| 90 90 | 
             
            summary: Ruby library for SAML service providers
         | 
| 91 91 | 
             
            test_files: []
         | 
| 92 | 
            -
            has_rdoc: 
         |