Class: OneLogin::RubySaml::Response

Inherits:
SamlMessage show all
Includes:
ErrorHandling
Defined in:
lib/onelogin/ruby-saml/response.rb

Overview

SAML2 Authentication Response. SAML Response

Constant Summary collapse

ASSERTION =
"urn:oasis:names:tc:SAML:2.0:assertion"
PROTOCOL =
"urn:oasis:names:tc:SAML:2.0:protocol"
DSIG =
"http://www.w3.org/2000/09/xmldsig#"
XENC =
"http://www.w3.org/2001/04/xmlenc#"
SAML_NAMESPACES =
{
  "p" => PROTOCOL,
  "a" => ASSERTION
}.freeze
AVAILABLE_OPTIONS =

Response available options This is not a whitelist to allow people extending OneLogin::RubySaml:Response and pass custom options

[
  :allowed_clock_drift, :check_duplicated_attributes, :matches_request_id, :settings, :skip_audience, :skip_authnstatement, :skip_conditions,
  :skip_destination, :skip_recipient_check, :skip_subject_confirmation
]

Constants inherited from SamlMessage

SamlMessage::BASE64_FORMAT

Instance Attribute Summary collapse

Attributes included from ErrorHandling

#errors

Instance Method Summary collapse

Methods included from ErrorHandling

#append_error, #reset_errors!

Methods inherited from SamlMessage

#id, schema, #valid_saml?, #version

Constructor Details

#initialize(response, options = {}) ⇒ Response

Constructs the SAML Response. A Response Object that is an extension of the SamlMessage class.

Parameters:

  • response (String)

    A UUEncoded SAML response from the IdP.

  • options (Hash) (defaults to: {})

    :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 or skip the recipient validation of the subject confirmation element with :skip_recipient_check option or skip the audience validation with :skip_audience option

Raises:

  • (ArgumentError)


56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/onelogin/ruby-saml/response.rb', line 56

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, settings)
  @document = XMLSecurity::SignedDocument.new(@response, @errors)

  if assertion_encrypted?
    @decrypted_document = generate_decrypted_document
  end
end

Instance Attribute Details

#decrypted_documentObject (readonly)

Returns the value of attribute decrypted_document.



31
32
33
# File 'lib/onelogin/ruby-saml/response.rb', line 31

def decrypted_document
  @decrypted_document
end

#documentObject (readonly)

Returns the value of attribute document.



30
31
32
# File 'lib/onelogin/ruby-saml/response.rb', line 30

def document
  @document
end

#optionsObject (readonly)

Returns the value of attribute options.



33
34
35
# File 'lib/onelogin/ruby-saml/response.rb', line 33

def options
  @options
end

#responseObject (readonly)

Returns the value of attribute response.



32
33
34
# File 'lib/onelogin/ruby-saml/response.rb', line 32

def response
  @response
end

#settingsObject

OneLogin::RubySaml::Settings Toolkit settings



28
29
30
# File 'lib/onelogin/ruby-saml/response.rb', line 28

def settings
  @settings
end

#softObject

Returns the value of attribute soft.



35
36
37
# File 'lib/onelogin/ruby-saml/response.rb', line 35

def soft
  @soft
end

Instance Method Details

#allowed_clock_driftFloat

returns the allowed clock drift on timing validation

Returns:

  • (Float)


366
367
368
# File 'lib/onelogin/ruby-saml/response.rb', line 366

def allowed_clock_drift
  options[:allowed_clock_drift].to_f.abs + Float::EPSILON
end

#assertion_encrypted?Boolean

Checks if the SAML Response contains or not an EncryptedAssertion element

Returns:

  • (Boolean)

    True if the SAML Response contains an EncryptedAssertion element



373
374
375
376
377
378
379
# File 'lib/onelogin/ruby-saml/response.rb', line 373

def assertion_encrypted?
  ! REXML::XPath.first(
    document,
    "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
    SAML_NAMESPACES
  ).nil?
end

#assertion_idObject



385
386
387
388
389
390
# File 'lib/onelogin/ruby-saml/response.rb', line 385

def assertion_id
  @assertion_id ||= begin
    node = xpath_first_from_signed_assertion("")
    node.nil? ? nil : node.attributes['ID']
  end
end

#attributesAttributes

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']

Returns:

  • (Attributes)

    OneLogin::RubySaml::Attributes enumerable collection.

Raises:



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/onelogin/ruby-saml/response.rb', line 151

def attributes
  @attr_statements ||= begin
    attributes = Attributes.new

    stmt_elements = xpath_from_signed_assertion('/a:AttributeStatement')
    stmt_elements.each do |stmt_element|
      stmt_element.elements.each do |attr_element|
        if attr_element.name == "EncryptedAttribute"
          node = decrypt_attribute(attr_element.dup)
        else
          node = attr_element
        end

        name  = node.attributes["Name"]

        if options[:check_duplicated_attributes] && attributes.include?(name)
          raise ValidationError.new("Found an Attribute element with duplicated Name")
        end

        values = node.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 : Utils.element_text(e)
          # 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 do |n|
              base_path = n.attributes['NameQualifier'] ? "#{n.attributes['NameQualifier']}/" : ''
              "#{base_path}#{Utils.element_text(n)}"
            end
          end
        }

        attributes.add(name, values.flatten)
      end
    end
    attributes
  end
end

#audiencesArray

Returns The Audience elements from the Contitions of the SAML Response.

Returns:

  • (Array)

    The Audience elements from the Contitions of the SAML Response.



357
358
359
360
361
362
# File 'lib/onelogin/ruby-saml/response.rb', line 357

def audiences
  @audiences ||= begin
    nodes = xpath_from_signed_assertion('/a:Conditions/a:AudienceRestriction/a:Audience')
    nodes.map { |node| Utils.element_text(node) }.reject(&:empty?)
  end
end

#authn_context_class_refString

Gets the AuthnContextClassRef from the AuthnStatement Could be used to require re-authentication if the assertion did not met the requested authentication context class.

Returns:

  • (String)

    AuthnContextClassRef value



222
223
224
# File 'lib/onelogin/ruby-saml/response.rb', line 222

def authn_context_class_ref
  @authn_context_class_ref ||= Utils.element_text(xpath_first_from_signed_assertion('/a:AuthnStatement/a:AuthnContext/a:AuthnContextClassRef'))
end

#authn_instantString

Gets the AuthnInstant from the AuthnStatement. Could be used to require re-authentication if a long time has passed since the last user authentication.

Returns:

  • (String)

    AuthnInstant value



210
211
212
213
214
215
# File 'lib/onelogin/ruby-saml/response.rb', line 210

def authn_instant
  @authn_instant ||= begin
    node = xpath_first_from_signed_assertion('/a:AuthnStatement')
    node.nil? ? nil : node.attributes['AuthnInstant']
  end
end

#conditionsREXML::Element

Gets the Condition Element of the SAML Response if exists. (returns the first node that matches the supplied xpath)

Returns:

  • (REXML::Element)

    Conditions Element if exists



283
284
285
# File 'lib/onelogin/ruby-saml/response.rb', line 283

def conditions
  @conditions ||= xpath_first_from_signed_assertion('/a:Conditions')
end

#destinationString|nil

Returns Destination attribute from the SAML Response.

Returns:

  • (String|nil)

    Destination attribute from the SAML Response.



344
345
346
347
348
349
350
351
352
353
# File 'lib/onelogin/ruby-saml/response.rb', line 344

def destination
  @destination ||= begin
    node = REXML::XPath.first(
      document,
      "/p:Response",
      { "p" => PROTOCOL }
    )
    node.nil? ? nil : node.attributes['Destination']
  end
end

#in_response_toString|nil

Returns The InResponseTo attribute from the SAML Response.

Returns:

  • (String|nil)

    The InResponseTo attribute from the SAML Response.



331
332
333
334
335
336
337
338
339
340
# File 'lib/onelogin/ruby-saml/response.rb', line 331

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) ⇒ Boolean

Validates the SAML Response with the default values (soft = true)

Parameters:

  • collect_errors (Boolean) (defaults to: false)

    Stop validation when first error appears or keep validating. (if soft=true)

Returns:

  • (Boolean)

    TRUE if the SAML Response is valid



82
83
84
# File 'lib/onelogin/ruby-saml/response.rb', line 82

def is_valid?(collect_errors = false)
  validate(collect_errors)
end

#issuersArray

Gets the Issuers (from Response and Assertion). (returns the first node that matches the supplied xpath from the Response and from the Assertion)

Returns:

  • (Array)

    Array with the Issuers (REXML::Element)



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/onelogin/ruby-saml/response.rb', line 305

def issuers
  @issuers ||= begin
    issuer_response_nodes = REXML::XPath.match(
      document,
      "/p:Response/a:Issuer",
      SAML_NAMESPACES
    )

    unless issuer_response_nodes.size == 1
      error_msg = "Issuer of the Response not found or multiple."
      raise ValidationError.new(error_msg)
    end

    issuer_assertion_nodes = xpath_from_signed_assertion("/a:Issuer")
    unless issuer_assertion_nodes.size == 1
      error_msg = "Issuer of the Assertion not found or multiple."
      raise ValidationError.new(error_msg)
    end

    nodes = issuer_response_nodes + issuer_assertion_nodes
    nodes.map { |node| Utils.element_text(node) }.compact.uniq
  end
end

#name_idString Also known as: nameid

Returns the NameID provided by the SAML response from the IdP.

Returns:

  • (String)

    the NameID provided by the SAML response from the IdP.



88
89
90
# File 'lib/onelogin/ruby-saml/response.rb', line 88

def name_id
  @name_id ||= Utils.element_text(name_id_node)
end

#name_id_formatString Also known as: nameid_format

Returns the NameID Format provided by the SAML response from the IdP.

Returns:

  • (String)

    the NameID Format provided by the SAML response from the IdP.



96
97
98
99
100
101
# File 'lib/onelogin/ruby-saml/response.rb', line 96

def name_id_format
  @name_id_format ||=
    if name_id_node && name_id_node.attribute("Format")
      name_id_node.attribute("Format").value
    end
end

#name_id_namequalifierString

Returns the NameID NameQualifier provided by the SAML response from the IdP.

Returns:

  • (String)

    the NameID NameQualifier provided by the SAML response from the IdP.



116
117
118
119
120
121
# File 'lib/onelogin/ruby-saml/response.rb', line 116

def name_id_namequalifier
  @name_id_namequalifier ||=
    if name_id_node && name_id_node.attribute("NameQualifier")
      name_id_node.attribute("NameQualifier").value
    end
end

#name_id_spnamequalifierString

Returns the NameID SPNameQualifier provided by the SAML response from the IdP.

Returns:

  • (String)

    the NameID SPNameQualifier provided by the SAML response from the IdP.



107
108
109
110
111
112
# File 'lib/onelogin/ruby-saml/response.rb', line 107

def name_id_spnamequalifier
  @name_id_spnamequalifier ||=
    if name_id_node && name_id_node.attribute("SPNameQualifier")
      name_id_node.attribute("SPNameQualifier").value
    end
end

#not_beforeTime

Gets the NotBefore Condition Element value.

Returns:

  • (Time)

    The NotBefore value in Time format



290
291
292
# File 'lib/onelogin/ruby-saml/response.rb', line 290

def not_before
  @not_before ||= parse_time(conditions, "NotBefore")
end

#not_on_or_afterTime

Gets the NotOnOrAfter Condition Element value.

Returns:

  • (Time)

    The NotOnOrAfter value in Time format



297
298
299
# File 'lib/onelogin/ruby-saml/response.rb', line 297

def not_on_or_after
  @not_on_or_after ||= parse_time(conditions, "NotOnOrAfter")
end

#response_idObject



381
382
383
# File 'lib/onelogin/ruby-saml/response.rb', line 381

def response_id
  id(document)
end

#session_expires_atString

Gets the SessionNotOnOrAfter from the AuthnStatement. Could be used to set the local session expiration (expire at latest)

Returns:

  • (String)

    The SessionNotOnOrAfter value



198
199
200
201
202
203
# File 'lib/onelogin/ruby-saml/response.rb', line 198

def session_expires_at
  @expires_at ||= begin
    node = xpath_first_from_signed_assertion('/a:AuthnStatement')
    node.nil? ? nil : parse_time(node, "SessionNotOnOrAfter")
  end
end

#sessionindexString

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

Returns:

  • (String)

    SessionIndex Value



129
130
131
132
133
134
# File 'lib/onelogin/ruby-saml/response.rb', line 129

def sessionindex
  @sessionindex ||= begin
    node = xpath_first_from_signed_assertion('/a:AuthnStatement')
    node.nil? ? nil : node.attributes['SessionIndex']
  end
end

#status_codeString

Returns StatusCode value from a SAML Response.

Returns:

  • (String)

    StatusCode value from a SAML Response.



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/onelogin/ruby-saml/response.rb', line 235

def status_code
  @status_code ||= begin
    nodes = REXML::XPath.match(
      document,
      "/p:Response/p:Status/p:StatusCode",
      { "p" => PROTOCOL }
    )
    if nodes.size == 1
      node = nodes[0]
      code = node.attributes["Value"] if node && node.attributes

      unless code == "urn:oasis:names:tc:SAML:2.0:status:Success"
        nodes = REXML::XPath.match(
          document,
          "/p:Response/p:Status/p:StatusCode/p:StatusCode",
          { "p" => PROTOCOL }
        )
        statuses = nodes.collect do |inner_node|
          inner_node.attributes["Value"]
        end

        code = [code, statuses].flatten.join(" | ")
      end

      code
    end
  end
end

#status_messageString

Returns the StatusMessage value from a SAML Response.

Returns:

  • (String)

    the StatusMessage value from a SAML Response.



266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/onelogin/ruby-saml/response.rb', line 266

def status_message
  @status_message ||= begin
    nodes = REXML::XPath.match(
      document,
      "/p:Response/p:Status/p:StatusMessage",
      { "p" => PROTOCOL }
    )
    if nodes.size == 1
      Utils.element_text(nodes.first)
    end
  end
end

#success?Boolean

Checks if the Status has the “Success” code

Returns:

  • (Boolean)

    True if the StatusCode is Sucess



229
230
231
# File 'lib/onelogin/ruby-saml/response.rb', line 229

def success?
  status_code == "urn:oasis:names:tc:SAML:2.0:status:Success"
end