class SAML2::Message

@abstract
that in this gem as a common base class.
ancestor, but they have several things in common so it’s useful to represent
In the SAML Schema, Request and Response don’t technically share a common

def build(message)

should be called from inside the specific request element
def build(message)
  message.parent['ID'] = id
  message.parent['Version'] = '2.0'
  message.parent['IssueInstant'] = issue_instant.iso8601
  message.parent['Destination'] = destination if destination
  issuer.build(message, element: 'Issuer') if issuer
end

def destination

Returns:
  • (String, nil) -
def destination
  if xml && !instance_variable_defined?(:@destination)
    @destination = xml['Destination']
  end
  @destination
end

def from_xml(node)

Raises:
  • (UnknownMessage) - If the element doesn't correspond to a known

Returns:
  • (Message) -

Parameters:
  • node (Nokogiri::XML::Element) --
def from_xml(node)
  return super unless self == Message
  klass = Message.known_messages[node.name]
  raise UnknownMessage.new("Unknown message #{node.name}") unless klass
  klass.from_xml(node)
end

def from_xml(node)

(see Base#from_xml)
def from_xml(node)
  super
  @id = nil
  @issue_instant = nil
end

def id

Returns:
  • (String) -
def id
  @id ||= xml['ID']
end

def inherited(klass)

def inherited(klass)
  # explicitly keep track of all messages in this base class
  Message.known_messages[klass.name.sub(/^SAML2::/, '')] = klass
end

def initialize

def initialize
  super
  @errors = []
  @id = "_#{SecureRandom.uuid}"
  @issue_instant = Time.now.utc
end

def issue_instant

Returns:
  • (Time) -
def issue_instant
  @issue_instant ||= Time.parse(xml['IssueInstant'])
end

def issuer

Returns:
  • (NameID, nil) -
def issuer
  @issuer ||= NameID.from_xml(xml.at_xpath('saml:Issuer', Namespaces::ALL))
end

def known_messages

def known_messages
  @known_messages ||= {}
end

def parse(xml)

Raises:
  • (UnexpectedMessage) -

Returns:
  • (Message) -

Parameters:
  • xml (String, IO) -- Anything that can be passed to +Nokogiri::XML+.
def parse(xml)
  result = Message.from_xml(Nokogiri::XML(xml) { |config| config.strict }.root)
  raise UnexpectedMessage.new("Expected a #{self.name}, but got a #{result.class.name}") unless self == Message || result.class == self
  result
rescue Nokogiri::XML::SyntaxError
  raise CorruptMessage
end

def sign(x509_certificate, private_key, algorithm_name = :sha256)

(see Signable#sign)
def sign(x509_certificate, private_key, algorithm_name = :sha256)
  super
  xml = @document.root
  # the Signature element must be right after the Issuer, so put it there
  issuer = xml.at_xpath("saml:Issuer", Namespaces::ALL)
  signature = xml.at_xpath("dsig:Signature", Namespaces::ALL)
  issuer.add_next_sibling(signature)
  self
end

def valid_schema?

Returns:
  • (Boolean) -
def valid_schema?
  return false unless Schemas.protocol.valid?(xml.document)
  true
end

def validate

def validate
  @errors = Schemas.protocol.validate(xml.document)
  errors
end

def validate_signature(fingerprint: nil,

Parameters:
  • verification_time () --
def validate_signature(fingerprint: nil,
                       cert: nil,
                       verification_time: issue_instant,
                       allow_expired_certificate: false)
  # verify the signature (certificate's validity) as of the time the message was generated
  super(fingerprint: fingerprint,
        cert: cert,
        verification_time: verification_time,
        allow_expired_certificate: allow_expired_certificate)
end