require "xml_security" require "time" require "base64" require "zlib" module OneLogin module RubySaml class Logoutresponse ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion" PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol" # For API compability, this is mutable. attr_accessor :settings attr_reader :document attr_reader :response attr_reader :options # # In order to validate that the response matches a given request, append # the option: # :matches_request_id => REQUEST_ID # # It will validate that the logout response matches the ID of the request. # You can also do this yourself through the in_response_to accessor. # def initialize(response, settings = nil, options = {}) raise ArgumentError.new("Logoutresponse cannot be nil") if response.nil? self.settings = settings @options = options @response = decode_raw_response(response) @document = XMLSecurity::SignedDocument.new(response) end def validate! validate(false) end def validate(soft = true) return false unless valid_saml?(soft) && valid_state?(soft) valid_in_response_to?(soft) && valid_issuer?(soft) && success?(soft) end def success?(soft = true) unless status_code == "urn:oasis:names:tc:SAML:2.0:status:Success" return soft ? false : validation_error("Bad status code. Expected , but was: <#@status_code> ") end true end def in_response_to @in_response_to ||= begin node = REXML::XPath.first(document, "/p:LogoutResponse", { "p" => PROTOCOL, "a" => ASSERTION }) node.nil? ? nil : node.attributes['InResponseTo'] end end def issuer @issuer ||= begin node = REXML::XPath.first(document, "/p:LogoutResponse/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION }) node ||= REXML::XPath.first(document, "/p:LogoutResponse/a:Assertion/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION }) node.nil? ? nil : node.text end end def status_code @status_code ||= begin node = REXML::XPath.first(document, "/p:LogoutResponse/p:Status/p:StatusCode", { "p" => PROTOCOL, "a" => ASSERTION }) node.nil? ? nil : node.attributes["Value"] end end private def decode(encoded) Base64.decode64(encoded) end def inflate(deflated) zlib = Zlib::Inflate.new(-Zlib::MAX_WBITS) zlib.inflate(deflated) end def decode_raw_response(response) if response =~ /^, but was: <#{in_response_to}>") end true end def valid_issuer?(soft = true) unless URI.parse(issuer) == URI.parse(self.settings.issuer) return soft ? false : validation_error("Doesn't match the issuer, expected: <#{self.settings.issuer}>, but was: <#{issuer}>") end true end def validation_error(message) raise ValidationError.new(message) end end end end