class Samlsso::Response
Constants
- ASSERTION
- DSIG
- PROTOCOL
Attributes
decoded_document[R]
decoded_response[R]
decrypted_response[R]
document[R]
errors[RW]
options[R]
response[R]
settings[RW]
TODO: This should probably be ctor initialized too… WDYT?
Public Class Methods
new(response, options = {})
click to toggle source
# File lib/samlsso/response.rb, line 24 def initialize(response, options = {}) @errors = [] raise ArgumentError.new("Response cannot be nil") if response.nil? @options = options @decoded_response = decode_raw_saml(response) @decrypted_response = decrypt_saml(@decoded_response, @options[:private_key_file_path]) @response = @decrypted_response @document = XMLSecurity::SignedDocument.new(@response, @errors) @decoded_document = XMLSecurity::SignedDocument.new(@decoded_response, @errors) end
Public Instance Methods
attributes()
click to toggle source
Returns Samlsso::Attributes
enumerable collection. All attributes can be iterated over attributes.each
or returned as array by attributes.all
For backwards compatibility samlsso 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:
Samlsso::Attributes.single_value_compatibility = false
Now this will return an array:
attributes['name']
# File lib/samlsso/response.rb, line 73 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"] ? attr_element.attributes["Name"] : attr_element.attributes["name"] values = attr_element.elements.collect{|e| # 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 } attributes.add(name, values) end attributes end end
conditions()
click to toggle source
Conditions (if any) for the assertion to run
# File lib/samlsso/response.rb, line 119 def conditions @conditions ||= xpath_first_from_signed_assertion('/a:Conditions') end
is_valid?()
click to toggle source
# File lib/samlsso/response.rb, line 35 def is_valid? validate end
issuer()
click to toggle source
# File lib/samlsso/response.rb, line 131 def issuer @issuer ||= begin node = REXML::XPath.first(document, "/p:Response/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION }) node ||= xpath_first_from_signed_assertion('/a:Issuer') node.nil? ? nil : node.text end end
name_id()
click to toggle source
The value of the user identifier as designated by the initialization request response
# File lib/samlsso/response.rb, line 48 def name_id @name_id ||= begin node = xpath_first_from_signed_assertion('/a:Subject/a:NameID') node.nil? ? nil : node.text end end
not_before()
click to toggle source
# File lib/samlsso/response.rb, line 123 def not_before @not_before ||= parse_time(conditions, "NotBefore") end
not_on_or_after()
click to toggle source
# File lib/samlsso/response.rb, line 127 def not_on_or_after @not_on_or_after ||= parse_time(conditions, "NotOnOrAfter") end
session_expires_at()
click to toggle source
When this user session should expire at latest
# File lib/samlsso/response.rb, line 96 def session_expires_at @expires_at ||= begin node = xpath_first_from_signed_assertion('/a:AuthnStatement') parse_time(node, "SessionNotOnOrAfter") end end
sessionindex()
click to toggle source
# File lib/samlsso/response.rb, line 55 def sessionindex @sessionindex ||= begin node = xpath_first_from_signed_assertion('/a:AuthnStatement') node.nil? ? nil : (node.attributes['SessionIndex'] ? node.attributes['SessionIndex'] : node.attributes['sessionindex']) end end
status_message()
click to toggle source
# File lib/samlsso/response.rb, line 111 def status_message @status_message ||= begin node = REXML::XPath.first(document, "/p:Response/p:Status/p:StatusMessage", { "p" => PROTOCOL, "a" => ASSERTION }) node.text if node end end
success?()
click to toggle source
Checks the status of the response for a “Success” code
# File lib/samlsso/response.rb, line 104 def success? @status_code ||= begin node = REXML::XPath.first(document, "/p:Response/p:Status/p:StatusCode", { "p" => PROTOCOL, "a" => ASSERTION }) node.attributes["Value"] == "urn:oasis:names:tc:SAML:2.0:status:Success" || node.attributes["value"] == "urn:oasis:names:tc:SAML:2.0:status:Success" end end
validate!()
click to toggle source
# File lib/samlsso/response.rb, line 39 def validate! validate(false) end
Private Instance Methods
get_fingerprint()
click to toggle source
# File lib/samlsso/response.rb, line 227 def get_fingerprint if settings.idp_cert cert = OpenSSL::X509::Certificate.new(settings.idp_cert) Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":") else settings.idp_cert_fingerprint end end
parse_time(node, attribute)
click to toggle source
# File lib/samlsso/response.rb, line 264 def parse_time(node, attribute) if node attrs = node.attributes[attribute] ? node.attributes[attribute] : node.attributes[attribute.downcase] Time.parse(attrs) if attrs end end
validate(soft = true)
click to toggle source
# File lib/samlsso/response.rb, line 141 def validate(soft = true) valid_saml?(decoded_document, soft) && validate_response_state(soft) && validate_conditions(soft) && validate_issuer(soft) && decoded_document.validate_document(get_fingerprint, soft) && validate_success_status(soft) end
validate_conditions(soft = true)
click to toggle source
# File lib/samlsso/response.rb, line 236 def validate_conditions(soft = true) return true if conditions.nil? return true if options[:skip_conditions] now = Time.now.utc if not_before && (now + (options[:allowed_clock_drift] || 0)) < not_before @errors << "Current time is earlier than NotBefore condition #{(now + (options[:allowed_clock_drift] || 0))} < #{not_before})" return soft ? false : validation_error("Current time is earlier than NotBefore condition") end if not_on_or_after && now >= not_on_or_after @errors << "Current time is on or after NotOnOrAfter condition (#{now} >= #{not_on_or_after})" return soft ? false : validation_error("Current time is on or after NotOnOrAfter condition") end true end
validate_issuer(soft = true)
click to toggle source
# File lib/samlsso/response.rb, line 255 def validate_issuer(soft = true) return true if settings.idp_entity_id.nil? unless URI.parse(issuer) == URI.parse(settings.idp_entity_id) return soft ? false : validation_error("Doesn't match the issuer, expected: <#{settings.idp_entity_id}>, but was: <#{issuer}>") end true end
validate_response_state(soft = true)
click to toggle source
# File lib/samlsso/response.rb, line 175 def validate_response_state(soft = true) if response.empty? return soft ? false : validation_error("Blank response") end if settings.nil? return soft ? false : validation_error("No settings on response") end if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil? return soft ? false : validation_error("No fingerprint or certificate on settings") end true end
validate_structure(soft = true)
click to toggle source
# File lib/samlsso/response.rb, line 158 def validate_structure(soft = true) return true #temp xml = Nokogiri::XML(self.document.to_s) SamlMessage.schema.validate(xml).map do |error| if soft @errors << "Schema validation failed" break false else error_message = [error.message, xml.to_s].join("\n\n") @errors << error_message validation_error(error_message) end end end
validate_success_status(soft = true)
click to toggle source
# File lib/samlsso/response.rb, line 150 def validate_success_status(soft = true) if success? true else soft ? false : validation_error(status_message) end end
xpath_first_from_signed_assertion(subelt=nil)
click to toggle source
# File lib/samlsso/response.rb, line 191 def xpath_first_from_signed_assertion(subelt=nil) id_str = "" id_str = "[@ID=$id]" unless document.signed_element_id.blank? node = REXML::XPath.first( document, "/p:Response/a:Assertion#{id_str}#{subelt}", { "p" => PROTOCOL, "a" => ASSERTION }, { 'id' => document.signed_element_id } ) node ||= REXML::XPath.first( document, "/p:Response#{id_str}/a:Assertion#{subelt}", { "p" => PROTOCOL, "a" => ASSERTION }, { 'id' => document.signed_element_id } ) node ||= REXML::XPath.first( document, "/p:Response/a:assertion#{id_str}#{subelt}", { "p" => PROTOCOL, "a" => ASSERTION }, { 'id' => document.signed_element_id } ) node ||= REXML::XPath.first( document, "/p:Response#{id_str}/a:assertion#{subelt}", { "p" => PROTOCOL, "a" => ASSERTION }, { 'id' => document.signed_element_id } ) node ||= REXML::XPath.first( document, "/p:Response#{id_str}/a:assertion#{subelt.downcase}", { "p" => PROTOCOL, "a" => ASSERTION }, { 'id' => document.signed_element_id } ) node end