class OneLogin::RubySaml::Response

SAML2 Authentication Response. SAML Response

Constants

ASSERTION
DSIG
PROTOCOL
XENC

Attributes

decrypted_document[R]
document[R]
options[R]
response[R]
settings[RW]

OneLogin::RubySaml::Settings Toolkit settings

soft[RW]

Public Class Methods

new(response, options = {}) click to toggle source

Constructs the SAML Response. A Response Object that is an extension of the SamlMessage class. @param response [String] A UUEncoded SAML response from the IdP. @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object

Or some options for the response validation process like skip the conditions validation
with the :skip_conditions, or allow a clock_drift when checking dates with :allowed_clock_drift
or :matches_request_id that will validate that the response matches the ID of the request,
or skip the subject confirmation validation with the :skip_subject_confirmation option
# File lib/onelogin/ruby-saml/response.rb, line 40
def initialize(response, options = {})
  raise ArgumentError.new("Response cannot be nil") if response.nil?

  @errors = []
  @options = options
  @soft = true
  unless options[:settings].nil?
    @settings = options[:settings]
    unless @settings.soft.nil?
      @soft = @settings.soft
    end
  end

  @response = decode_raw_saml(response)
  @document = XMLSecurity::SignedDocument.new(@response, @errors)

  if assertion_encrypted?
    @decrypted_document = generate_decrypted_document
  end
end

Public Instance Methods

allowed_clock_drift() click to toggle source

returns the allowed clock drift on timing validation @return [Integer]

# File lib/onelogin/ruby-saml/response.rb, line 280
def allowed_clock_drift
  return options[:allowed_clock_drift] || 0
end
attributes() click to toggle source

Gets the Attributes from the AttributeStatement element.

All attributes can be iterated over attributes.each or returned as array by attributes.all For backwards compatibility ruby-saml returns by default only the first value for a given attribute with

attributes['name']

To get all of the attributes, use:

attributes.multi('name')

Or turn off the compatibility:

OneLogin::RubySaml::Attributes.single_value_compatibility = false

Now this will return an array:

attributes['name']

@return [Attributes] OneLogin::RubySaml::Attributes enumerable collection.

# File lib/onelogin/ruby-saml/response.rb, line 119
def attributes
  @attr_statements ||= begin
    attributes = Attributes.new

    stmt_element = xpath_first_from_signed_assertion('/a:AttributeStatement')
    return attributes if stmt_element.nil?

    stmt_element.elements.each do |attr_element|
      name  = attr_element.attributes["Name"]
      values = attr_element.elements.collect{|e|
        if (e.elements.nil? || e.elements.size == 0)
          # SAMLCore requires that nil AttributeValues MUST contain xsi:nil XML attribute set to "true" or "1"
          # otherwise the value is to be regarded as empty.
          ["true", "1"].include?(e.attributes['xsi:nil']) ? nil : e.text.to_s
        # explicitly support saml2:NameID with saml2:NameQualifier if supplied in attributes
        # this is useful for allowing eduPersonTargetedId to be passed as an opaque identifier to use to 
        # identify the subject in an SP rather than email or other less opaque attributes
        # NameQualifier, if present is prefixed with a "/" to the value
        else 
         REXML::XPath.match(e,'a:NameID', { "a" => ASSERTION }).collect{|n|
            (n.attributes['NameQualifier'] ? n.attributes['NameQualifier'] +"/" : '') + n.text.to_s
          }
        end
      }

      attributes.add(name, values.flatten)
    end

    attributes
  end
end
audiences() click to toggle source

@return [Array] The Audience elements from the Contitions of the SAML Response.

# File lib/onelogin/ruby-saml/response.rb, line 265
def audiences
  @audiences ||= begin
    audiences = []
    nodes = xpath_from_signed_assertion('/a:Conditions/a:AudienceRestriction/a:Audience')
    nodes.each do |node|
      if node && node.text
        audiences << node.text
      end
    end
    audiences
  end
end
conditions() click to toggle source

Gets the Condition Element of the SAML Response if exists. (returns the first node that matches the supplied xpath) @return [REXML::Element] Conditions Element if exists

# File lib/onelogin/ruby-saml/response.rb, line 199
def conditions
  @conditions ||= xpath_first_from_signed_assertion('/a:Conditions')
end
destination() click to toggle source

@return [String|nil] Destination attribute from the SAML Response.

# File lib/onelogin/ruby-saml/response.rb, line 252
def destination
  @destination ||= begin
    node = REXML::XPath.first(
      document,
      "/p:Response",
      { "p" => PROTOCOL }
    )
    node.nil? ? nil : node.attributes['Destination']
  end
end
in_response_to() click to toggle source

@return [String|nil] The InResponseTo attribute from the SAML Response.

# File lib/onelogin/ruby-saml/response.rb, line 239
def in_response_to
  @in_response_to ||= begin
    node = REXML::XPath.first(
      document,
      "/p:Response",
      { "p" => PROTOCOL }
    )
    node.nil? ? nil : node.attributes['InResponseTo']
  end
end
is_valid?(collect_errors = false) click to toggle source

Validates the SAML Response with the default values (soft = true) @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true) @return [Boolean] TRUE if the SAML Response is valid

# File lib/onelogin/ruby-saml/response.rb, line 65
def is_valid?(collect_errors = false)
  validate(collect_errors)
end
issuers() click to toggle source

Gets the Issuers (from Response and Assertion). (returns the first node that matches the supplied xpath from the Response and from the Assertion) @return [Array] Array with the Issuers (REXML::Element)

# File lib/onelogin/ruby-saml/response.rb, line 221
def issuers
  @issuers ||= begin
    issuers = []
    nodes = REXML::XPath.match(
      document,
      "/p:Response/a:Issuer",
      { "p" => PROTOCOL, "a" => ASSERTION }
    )
    nodes += xpath_from_signed_assertion("/a:Issuer")
    nodes.each do |node|
      issuers << node.text if node.text
    end
    issuers.uniq
  end
end
name_id() click to toggle source

@return [String] the NameID provided by the SAML response from the IdP.

# File lib/onelogin/ruby-saml/response.rb, line 71
def name_id
  @name_id ||=
    if name_id_node
      name_id_node.text
    end
end
Also aliased as: nameid
name_id_format() click to toggle source

@return [String] the NameID Format provided by the SAML response from the IdP.

# File lib/onelogin/ruby-saml/response.rb, line 82
def name_id_format
  @name_id_format ||=
    if name_id_node && name_id_node.attribute("Format")
      name_id_node.attribute("Format").value
    end
end
Also aliased as: nameid_format
nameid()
Alias for: name_id
nameid_format()
Alias for: name_id_format
not_before() click to toggle source

Gets the NotBefore Condition Element value. @return [Time] The NotBefore value in Time format

# File lib/onelogin/ruby-saml/response.rb, line 206
def not_before
  @not_before ||= parse_time(conditions, "NotBefore")
end
not_on_or_after() click to toggle source

Gets the NotOnOrAfter Condition Element value. @return [Time] The NotOnOrAfter value in Time format

# File lib/onelogin/ruby-saml/response.rb, line 213
def not_on_or_after
  @not_on_or_after ||= parse_time(conditions, "NotOnOrAfter")
end
session_expires_at() click to toggle source

Gets the SessionNotOnOrAfter from the AuthnStatement. Could be used to set the local session expiration (expire at latest) @return [String] The SessionNotOnOrAfter value

# File lib/onelogin/ruby-saml/response.rb, line 155
def session_expires_at
  @expires_at ||= begin
    node = xpath_first_from_signed_assertion('/a:AuthnStatement')
    node.nil? ? nil : parse_time(node, "SessionNotOnOrAfter")
  end
end
sessionindex() click to toggle source

Gets the SessionIndex from the AuthnStatement. Could be used to be stored in the local session in order to be used in a future Logout Request that the SP could send to the IdP, to set what specific session must be deleted @return [String] SessionIndex Value

# File lib/onelogin/ruby-saml/response.rb, line 98
def sessionindex
  @sessionindex ||= begin
    node = xpath_first_from_signed_assertion('/a:AuthnStatement')
    node.nil? ? nil : node.attributes['SessionIndex']
  end
end
status_code() click to toggle source

@return [String] StatusCode value from a SAML Response.

# File lib/onelogin/ruby-saml/response.rb, line 171
def status_code
  @status_code ||= begin
    node = REXML::XPath.first(
      document,
      "/p:Response/p:Status/p:StatusCode",
      { "p" => PROTOCOL }
    )
    node.attributes["Value"] if node && node.attributes
  end
end
status_message() click to toggle source

@return [String] the StatusMessage value from a SAML Response.

# File lib/onelogin/ruby-saml/response.rb, line 184
def status_message
  @status_message ||= begin
    node = REXML::XPath.first(
      document,
      "/p:Response/p:Status/p:StatusMessage",
      { "p" => PROTOCOL }
    )
    node.text if node
  end
end
success?() click to toggle source

Checks if the Status has the “Success” code @return [Boolean] True if the StatusCode is Sucess

# File lib/onelogin/ruby-saml/response.rb, line 165
def success?
  status_code == "urn:oasis:names:tc:SAML:2.0:status:Success"
end

Private Instance Methods

assertion_encrypted?() click to toggle source

Checks if the SAML Response contains or not an EncryptedAssertion element @return [Boolean] True if the SAML Response contains an EncryptedAssertion element

# File lib/onelogin/ruby-saml/response.rb, line 786
def assertion_encrypted?
  ! REXML::XPath.first(
    document,
    "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
    { "p" => PROTOCOL, "a" => ASSERTION }
  ).nil?
end
decrypt_assertion(encrypted_assertion_node) click to toggle source

Decrypts an EncryptedAssertion element @param encrypted_assertion_node [REXML::Element] The EncryptedAssertion element @return [REXML::Document] The decrypted EncryptedAssertion element

# File lib/onelogin/ruby-saml/response.rb, line 798
def decrypt_assertion(encrypted_assertion_node)
  decrypt_element(encrypted_assertion_node, /(.*<\/(\w+:)?Assertion>)/m)
end
decrypt_assertion_from_document(document_copy) click to toggle source

Obtains a SAML Response with the EncryptedAssertion element decrypted @param document_copy [XMLSecurity::SignedDocument] A copy of the original SAML Response with the encrypted assertion @return [XMLSecurity::SignedDocument] The SAML Response with the assertion decrypted

# File lib/onelogin/ruby-saml/response.rb, line 767
def decrypt_assertion_from_document(document_copy)
  response_node = REXML::XPath.first(
    document_copy,
    "/p:Response/",
    { "p" => PROTOCOL }
  )
  encrypted_assertion_node = REXML::XPath.first(
    document_copy,
    "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
    { "p" => PROTOCOL, "a" => ASSERTION }
  )
  response_node.add(decrypt_assertion(encrypted_assertion_node))
  encrypted_assertion_node.remove
  XMLSecurity::SignedDocument.new(response_node.to_s)
end
decrypt_element(encrypt_node, rgrex) click to toggle source

Decrypt an element @param encryptedid_node [REXML::Element] The encrypted element @return [REXML::Document] The decrypted element

# File lib/onelogin/ruby-saml/response.rb, line 814
def decrypt_element(encrypt_node, rgrex)
  if settings.nil? || !settings.get_sp_key
    raise ValidationError.new('An ' + encrypt_node.name + ' found and no SP private key found on the settings to decrypt it')
  end

  elem_plaintext = OneLogin::RubySaml::Utils.decrypt_data(encrypt_node, settings.get_sp_key)
  # If we get some problematic noise in the plaintext after decrypting.
  # This quick regexp parse will grab only the Element and discard the noise.
  elem_plaintext = elem_plaintext.match(rgrex)[0]
  # To avoid namespace errors if saml namespace is not defined at assertion_plaintext
  # create a parent node first with the saml namespace defined
  elem_plaintext = '<node xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">' + elem_plaintext + '</node>'
  doc = REXML::Document.new(elem_plaintext)
  doc.root[0]
end
decrypt_nameid(encryptedid_node) click to toggle source

Decrypts an EncryptedID element @param encryptedid_node [REXML::Element] The EncryptedID element @return [REXML::Document] The decrypted EncrypedtID element

# File lib/onelogin/ruby-saml/response.rb, line 806
def decrypt_nameid(encryptedid_node)
  decrypt_element(encryptedid_node, /(.*<\/(\w+:)?NameID>)/m)
end
generate_decrypted_document() click to toggle source

Generates the #decrypted_document @return [XMLSecurity::SignedDocument] The SAML Response with the assertion decrypted

# File lib/onelogin/ruby-saml/response.rb, line 748
def generate_decrypted_document
  if settings.nil? || !settings.get_sp_key
    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')
  end

  # Marshal at Ruby 1.8.7 throw an Exception
  if RUBY_VERSION < "1.9"
    document_copy = XMLSecurity::SignedDocument.new(response, errors)
  else
    document_copy = Marshal.load(Marshal.dump(document))
  end

  decrypt_assertion_from_document(document_copy)
end
name_id_node() click to toggle source
# File lib/onelogin/ruby-saml/response.rb, line 690
def name_id_node
  @name_id_node ||=
    begin
      encrypted_node = xpath_first_from_signed_assertion('/a:Subject/a:EncryptedID')
      if encrypted_node
        node = decrypt_nameid(encrypted_node)
      else
        node = xpath_first_from_signed_assertion('/a:Subject/a:NameID')
      end
    end
end
parse_time(node, attribute) click to toggle source

Parse the attribute of a given node in Time format @param node [REXML:Element] The node @param attribute [String] The attribute name @return [Time|nil] The parsed value

# File lib/onelogin/ruby-saml/response.rb, line 835
def parse_time(node, attribute)
  if node && node.attributes[attribute]
    Time.parse(node.attributes[attribute])
  end
end
validate(collect_errors = false) click to toggle source

Validates the SAML Response (calls several validation methods) @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true) @return [Boolean] True if the SAML Response is valid, otherwise False if soft=True @raise [ValidationError] if soft == false and validation fails

# File lib/onelogin/ruby-saml/response.rb, line 291
def validate(collect_errors = false)
  reset_errors!
  return false unless validate_response_state

  validations = [
    :validate_response_state,
    :validate_version,
    :validate_id,
    :validate_success_status,
    :validate_num_assertion,
    :validate_no_encrypted_attributes,
    :validate_signed_elements,
    :validate_structure,
    :validate_in_response_to,
    :validate_conditions,
    :validate_audience,
    :validate_destination,
    :validate_issuer,
    :validate_session_expiration,
    :validate_subject_confirmation,
    :validate_signature
  ]

  if collect_errors
    validations.each { |validation| send(validation) }
    @errors.empty?
  else
    validations.all? { |validation| send(validation) }
  end
end
validate_audience() click to toggle source

Validates the Audience, (If the Audience match the Service Provider EntityID) If fails, the error is added to the errors array @return [Boolean] True if there is an Audience Element that match the Service Provider EntityID, otherwise False if soft=True @raise [ValidationError] if soft == false and validation fails

# File lib/onelogin/ruby-saml/response.rb, line 514
def validate_audience
  return true if audiences.empty? || settings.issuer.nil? || settings.issuer.empty?

  unless audiences.include? settings.issuer
    error_msg = "#{settings.issuer} is not a valid audience for this Response - Valid audiences: #{audiences.join(',')}"
    return append_error(error_msg)
  end

  true
end
validate_conditions() click to toggle source

Validates the Conditions. (If the response was initialized with the :skip_conditions option, this validation is skipped, If the response was initialized with the :allowed_clock_drift option, the timing validations are relaxed by the #allowed_clock_drift value) @return [Boolean] True if satisfies the conditions, otherwise False if soft=True @raise [ValidationError] if soft == false and validation fails

# File lib/onelogin/ruby-saml/response.rb, line 545
def validate_conditions
  return true if conditions.nil?
  return true if options[:skip_conditions]

  now = Time.now.utc

  if not_before && (now + allowed_clock_drift) < not_before
    error_msg = "Current time is earlier than NotBefore condition #{(now + allowed_clock_drift)} < #{not_before})"
    return append_error(error_msg)
  end

  if not_on_or_after && now >= (not_on_or_after + allowed_clock_drift)
    error_msg = "Current time is on or after NotOnOrAfter condition (#{now} >= #{not_on_or_after + allowed_clock_drift})"
    return append_error(error_msg)
  end

  true
end
validate_destination() click to toggle source

Validates the Destination, (If the SAML Response is received where expected) If fails, the error is added to the errors array @return [Boolean] True if there is a Destination element that matches the Consumer Service URL, otherwise False

# File lib/onelogin/ruby-saml/response.rb, line 529
def validate_destination
  return true if destination.nil? || destination.empty? || settings.assertion_consumer_service_url.nil? || settings.assertion_consumer_service_url.empty?

  unless destination == settings.assertion_consumer_service_url
    error_msg = "The response was received at #{destination} instead of #{settings.assertion_consumer_service_url}"
    return append_error(error_msg)
  end

  true
end
validate_id() click to toggle source

Validates that the SAML Response contains an ID If fails, the error is added to the errors array. @return [Boolean] True if the SAML Response contains an ID, otherwise returns False

# File lib/onelogin/ruby-saml/response.rb, line 374
def validate_id
  unless id(document)
    return append_error("Missing ID attribute on SAML Response")
  end

  true
end
validate_in_response_to() click to toggle source

Validates if the provided request_id match the inResponseTo value. If fails, the error is added to the errors array @return [Boolean] True if there is no request_id or it match, otherwise False if soft=True @raise [ValidationError] if soft == false and validation fails

# File lib/onelogin/ruby-saml/response.rb, line 500
def validate_in_response_to
  return true unless options.has_key? :matches_request_id
  return true if options[:matches_request_id].nil? || options[:matches_request_id].empty?
  return true unless options[:matches_request_id] != in_response_to

  error_msg = "The InResponseTo of the Response: #{in_response_to}, does not match the ID of the AuthNRequest sent by the SP: #{options[:matches_request_id]}"
  append_error(error_msg)
end
validate_issuer() click to toggle source

Validates the Issuer (Of the SAML Response and the SAML Assertion) @param soft [Boolean] soft Enable or Disable the soft mode (In order to raise exceptions when the response is invalid or not) @return [Boolean] True if the Issuer matchs the IdP entityId, otherwise False if soft=True @raise [ValidationError] if soft == false and validation fails

# File lib/onelogin/ruby-saml/response.rb, line 569
def validate_issuer
  return true if settings.idp_entity_id.nil?

  issuers.each do |issuer|
    unless URI.parse(issuer) == URI.parse(settings.idp_entity_id)
      error_msg = "Doesn't match the issuer, expected: <#{settings.idp_entity_id}>, but was: <#{issuer}>"
      return append_error(error_msg)
    end
  end

  true
end
validate_no_encrypted_attributes() click to toggle source

Validates that there are not EncryptedAttribute (not supported) If fails, the error is added to the errors array @return [Boolean] True if there are no EncryptedAttribute elements, otherwise False if soft=True @raise [ValidationError] if soft == false and validation fails

# File lib/onelogin/ruby-saml/response.rb, line 422
def validate_no_encrypted_attributes
  nodes = xpath_from_signed_assertion("/a:AttributeStatement/a:EncryptedAttribute")        
  if nodes && nodes.length > 0
    return append_error("There is an EncryptedAttribute in the Response and this SP not support them")
  end

  true
end
validate_num_assertion() click to toggle source

Validates that the SAML Response only contains a single Assertion (encrypted or not). If fails, the error is added to the errors array. @return [Boolean] True if the SAML Response contains one unique Assertion, otherwise False

# File lib/onelogin/ruby-saml/response.rb, line 398
def validate_num_assertion
  assertions = REXML::XPath.match(
    document,
    "//a:Assertion",
    { "a" => ASSERTION }
  )
  encrypted_assertions = REXML::XPath.match(
    document,
    "//a:EncryptedAssertion",
    { "a" => ASSERTION }
  )

  unless assertions.size + encrypted_assertions.size == 1
    return append_error("SAML Response must contain 1 assertion")
  end

  true
end
validate_response_state() click to toggle source

Validates that the SAML Response provided in the initialization is not empty, also check that the setting and the IdP cert were also provided @return [Boolean] True if the required info is found, false otherwise

# File lib/onelogin/ruby-saml/response.rb, line 358
def validate_response_state
  return append_error("Blank response") if response.nil? || response.empty?

  return append_error("No settings on response") if settings.nil?

  if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil?
    return append_error("No fingerprint or certificate on settings")
  end

  true
end
validate_session_expiration(soft = true) click to toggle source

Validates that the Session haven't expired (If the response was initialized with the :allowed_clock_drift option, this time validation is relaxed by the #allowed_clock_drift value) If fails, the error is added to the errors array @param soft [Boolean] soft Enable or Disable the soft mode (In order to raise exceptions when the response is invalid or not) @return [Boolean] True if the SessionNotOnOrAfter of the AttributeStatement is valid, otherwise (when expired) False if soft=True @raise [ValidationError] if soft == false and validation fails

# File lib/onelogin/ruby-saml/response.rb, line 589
def validate_session_expiration(soft = true)
  return true if session_expires_at.nil?

  now = Time.now.utc
  unless (session_expires_at + allowed_clock_drift) > now
    error_msg = "The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response"
    return append_error(error_msg)
  end

  true
end
validate_signature() click to toggle source

Validates the Signature @return [Boolean] True if not contains a Signature or if the Signature is valid, otherwise False if soft=True @raise [ValidationError] if soft == false and validation fails

# File lib/onelogin/ruby-saml/response.rb, line 649
def validate_signature
  error_msg = "Invalid Signature on SAML Response"

  # If the response contains the signature, and the assertion was encrypted, validate the original SAML Response
  # otherwise, review if the decrypted assertion contains a signature
  sig_elements = REXML::XPath.match(
    document,
    "/p:Response[@ID=$id]/ds:Signature]",
    { "p" => PROTOCOL, "ds" => DSIG },
    { 'id' => document.signed_element_id }
  )

  use_original = sig_elements.size == 1 || decrypted_document.nil?
  doc = use_original ? document : decrypted_document

  # Check signature nodes
  if sig_elements.nil? || sig_elements.size == 0
    sig_elements = REXML::XPath.match(
      doc,
      "/p:Response/a:Assertion[@ID=$id]/ds:Signature",
      {"p" => PROTOCOL, "a" => ASSERTION, "ds"=>DSIG},
      { 'id' => doc.signed_element_id }
    )
  end

  if sig_elements.size != 1
    return append_error(error_msg)
  end

  opts = {}
  opts[:fingerprint_alg] = settings.idp_cert_fingerprint_algorithm
  opts[:cert] = settings.get_idp_cert
  fingerprint = settings.get_fingerprint

  unless fingerprint && doc.validate_document(fingerprint, @soft, opts)          
    return append_error(error_msg)
  end

  true
end
validate_signed_elements() click to toggle source

Validates the Signed elements If fails, the error is added to the errors array @return [Boolean] True if there is 1 or 2 Elements signed in the SAML Response

an are a Response or an Assertion Element, otherwise False if soft=True
# File lib/onelogin/ruby-saml/response.rb, line 437
def validate_signed_elements
  signature_nodes = REXML::XPath.match(
    decrypted_document.nil? ? document : decrypted_document,
    "//ds:Signature",
    {"ds"=>DSIG}
  )
  signed_elements = []
  verified_seis = []
  verified_ids = []
  signature_nodes.each do |signature_node|
    signed_element = signature_node.parent.name
    if signed_element != 'Response' && signed_element != 'Assertion'
      return append_error("Invalid Signature Element '#{signed_element}'. SAML Response rejected")
    end

    if signature_node.parent.attributes['ID'].nil?
      return append_error("Signed Element must contain an ID. SAML Response rejected")
    end

    id = signature_node.parent.attributes.get_attribute("ID").value
    if verified_ids.include?(id)
      return append_error("Duplicated ID. SAML Response rejected")
    end
    verified_ids.push(id)

    # Check that reference URI matches the parent ID and no duplicate References or IDs
    ref = REXML::XPath.first(signature_node, ".//ds:Reference", {"ds"=>DSIG})
    if ref
      uri = ref.attributes.get_attribute("URI")
      if uri && !uri.value.empty?
        sei = uri.value[1..-1]

        unless sei == id
          return append_error("Found an invalid Signed Element. SAML Response rejected")
        end

        if verified_seis.include?(sei)
          return append_error("Duplicated Reference URI. SAML Response rejected")
        end

        verified_seis.push(sei)
      end
    end

    signed_elements << signed_element
  end

  unless signature_nodes.length < 3 && !signed_elements.empty?
    return append_error("Found an unexpected number of Signature Element. SAML Response rejected")
  end

  if settings.security[:want_assertions_signed] && !(signed_elements.include? "Assertion")
    return append_error("The Assertion of the Response is not signed and the SP requires it")
  end

  true
end
validate_structure() click to toggle source

Validates the SAML Response against the specified schema. @return [Boolean] True if the XML is valid, otherwise False if soft=True @raise [ValidationError] if soft == false and validation fails

# File lib/onelogin/ruby-saml/response.rb, line 339
def validate_structure
  structure_error_msg = "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd"
  unless valid_saml?(document, soft)
    return append_error(structure_error_msg)
  end

  unless decrypted_document.nil?
    unless valid_saml?(decrypted_document, soft)
      return append_error(structure_error_msg)
    end
  end

  true
end
validate_subject_confirmation() click to toggle source

Validates if exists valid SubjectConfirmation (If the response was initialized with the :allowed_clock_drift option, timimg validation are relaxed by the #allowed_clock_drift value. If the response was initialized with the :skip_subject_confirmation option, this validation is skipped) If fails, the error is added to the errors array @return [Boolean] True if exists a valid SubjectConfirmation, otherwise False if soft=True @raise [ValidationError] if soft == false and validation fails

# File lib/onelogin/ruby-saml/response.rb, line 608
def validate_subject_confirmation
  return true if options[:skip_subject_confirmation]
  valid_subject_confirmation = false

  subject_confirmation_nodes = xpath_from_signed_assertion('/a:Subject/a:SubjectConfirmation')
  
  now = Time.now.utc
  subject_confirmation_nodes.each do |subject_confirmation|
    if subject_confirmation.attributes.include? "Method" and subject_confirmation.attributes['Method'] != 'urn:oasis:names:tc:SAML:2.0:cm:bearer'
      next
    end

    confirmation_data_node = REXML::XPath.first(
      subject_confirmation,
      'a:SubjectConfirmationData',
      { "a" => ASSERTION }
    )

    next unless confirmation_data_node

    attrs = confirmation_data_node.attributes
    next if (attrs.include? "InResponseTo" and attrs['InResponseTo'] != in_response_to) ||
            (attrs.include? "NotOnOrAfter" and (parse_time(confirmation_data_node, "NotOnOrAfter") + allowed_clock_drift) <= now) ||
            (attrs.include? "NotBefore" and parse_time(confirmation_data_node, "NotBefore") > (now + allowed_clock_drift))
    
    valid_subject_confirmation = true
    break
  end

  if !valid_subject_confirmation
    error_msg = "A valid SubjectConfirmation was not found on this Response"
    return append_error(error_msg)
  end

  true
end
validate_success_status() click to toggle source

Validates the Status of the SAML Response @return [Boolean] True if the SAML Response contains a Success code, otherwise False if soft == false @raise [ValidationError] if soft == false and validation fails

# File lib/onelogin/ruby-saml/response.rb, line 327
def validate_success_status
  return true if success?
    
  error_msg = 'The status code of the Response was not Success'
  status_error_msg = OneLogin::RubySaml::Utils.status_error_msg(error_msg, status_code, status_message)
  append_error(status_error_msg)
end
validate_version() click to toggle source

Validates the SAML version (2.0) If fails, the error is added to the errors array. @return [Boolean] True if the SAML Response is 2.0, otherwise returns False

# File lib/onelogin/ruby-saml/response.rb, line 386
def validate_version
  unless version(document) == "2.0"
    return append_error("Unsupported SAML version")
  end

  true
end
xpath_first_from_signed_assertion(subelt=nil) click to toggle source

Extracts the first appearance that matchs the subelt (pattern) Search on any Assertion that is signed, or has a Response parent signed @param subelt [String] The XPath pattern @return [REXML::Element | nil] If any matches, return the Element

# File lib/onelogin/ruby-saml/response.rb, line 707
def xpath_first_from_signed_assertion(subelt=nil)
  doc = decrypted_document.nil? ? document : decrypted_document
  node = REXML::XPath.first(
      doc,
      "/p:Response/a:Assertion[@ID=$id]#{subelt}",
      { "p" => PROTOCOL, "a" => ASSERTION },
      { 'id' => doc.signed_element_id }
  )
  node ||= REXML::XPath.first(
      doc,
      "/p:Response[@ID=$id]/a:Assertion#{subelt}",
      { "p" => PROTOCOL, "a" => ASSERTION },
      { 'id' => doc.signed_element_id }
  )
  node
end
xpath_from_signed_assertion(subelt=nil) click to toggle source

Extracts all the appearances that matchs the subelt (pattern) Search on any Assertion that is signed, or has a Response parent signed @param subelt [String] The XPath pattern @return [Array of REXML::Element] Return all matches

# File lib/onelogin/ruby-saml/response.rb, line 729
def xpath_from_signed_assertion(subelt=nil)
  doc = decrypted_document.nil? ? document : decrypted_document
  node = REXML::XPath.match(
      doc,
      "/p:Response/a:Assertion[@ID=$id]#{subelt}",
      { "p" => PROTOCOL, "a" => ASSERTION },
      { 'id' => doc.signed_element_id }
  )
  node.concat( REXML::XPath.match(
      doc,
      "/p:Response[@ID=$id]/a:Assertion#{subelt}",
      { "p" => PROTOCOL, "a" => ASSERTION },
      { 'id' => doc.signed_element_id }
  ))
end