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#"
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_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.

Raises:

  • (ArgumentError)

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/onelogin/ruby-saml/response.rb', line 50

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

Instance Attribute Details

#decrypted_documentObject (readonly)

Returns the value of attribute decrypted_document


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

def decrypted_document
  @decrypted_document
end

#documentObject (readonly)

Returns the value of attribute document


26
27
28
# File 'lib/onelogin/ruby-saml/response.rb', line 26

def document
  @document
end

#optionsObject (readonly)

Returns the value of attribute options


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

def options
  @options
end

#responseObject (readonly)

Returns the value of attribute response


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

def response
  @response
end

#settingsObject

OneLogin::RubySaml::Settings Toolkit settings


24
25
26
# File 'lib/onelogin/ruby-saml/response.rb', line 24

def settings
  @settings
end

#softObject

Returns the value of attribute soft


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

def soft
  @soft
end

Instance Method Details

#allowed_clock_driftInteger

returns the allowed clock drift on timing validation


340
341
342
# File 'lib/onelogin/ruby-saml/response.rb', line 340

def allowed_clock_drift
  return options[:allowed_clock_drift] || 0
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']

Raises:


145
146
147
148
149
150
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
# File 'lib/onelogin/ruby-saml/response.rb', line 145

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{|n|
              (n.attributes['NameQualifier'] ? n.attributes['NameQualifier'] +"/" : '') + Utils.element_text(n)
            }
          end
        }

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

#audiencesArray


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

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

#conditionsREXML::Element

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


256
257
258
# File 'lib/onelogin/ruby-saml/response.rb', line 256

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

#destinationString|nil


318
319
320
321
322
323
324
325
326
327
# File 'lib/onelogin/ruby-saml/response.rb', line 318

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


305
306
307
308
309
310
311
312
313
314
# File 'lib/onelogin/ruby-saml/response.rb', line 305

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)


76
77
78
# File 'lib/onelogin/ruby-saml/response.rb', line 76

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)


278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/onelogin/ruby-saml/response.rb', line 278

def issuers
  @issuers ||= begin
    issuer_response_nodes = REXML::XPath.match(
      document,
      "/p:Response/a:Issuer",
      { "p" => PROTOCOL, "a" => ASSERTION }
    )

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

    doc = decrypted_document.nil? ? document : decrypted_document
    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


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

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

#name_id_formatString Also known as: nameid_format


90
91
92
93
94
95
# File 'lib/onelogin/ruby-saml/response.rb', line 90

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


110
111
112
113
114
115
# File 'lib/onelogin/ruby-saml/response.rb', line 110

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


101
102
103
104
105
106
# File 'lib/onelogin/ruby-saml/response.rb', line 101

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.


263
264
265
# File 'lib/onelogin/ruby-saml/response.rb', line 263

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

#not_on_or_afterTime

Gets the NotOnOrAfter Condition Element value.


270
271
272
# File 'lib/onelogin/ruby-saml/response.rb', line 270

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

#session_expires_atString

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


191
192
193
194
195
196
# File 'lib/onelogin/ruby-saml/response.rb', line 191

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


123
124
125
126
127
128
# File 'lib/onelogin/ruby-saml/response.rb', line 123

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

#status_codeString


207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/onelogin/ruby-saml/response.rb', line 207

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 |node|
          node.attributes["Value"]
        end
        extra_code = statuses.join(" | ")
        if extra_code
          code = "#{code} | #{extra_code}"
        end
      end
      code
    end
  end
end

#status_messageString


239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/onelogin/ruby-saml/response.rb', line 239

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


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

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