module Samlr::Tools

Constants

SHA_MAP

Public Class Methods

algorithm(value) click to toggle source

Convert algorithm attribute value to Ruby implementation

# File lib/samlr/tools.rb, line 25
def self.algorithm(value)
  if value =~ /sha(\d+)$/
    implementation = SHA_MAP[$1.to_i]
  end

  implementation || OpenSSL::Digest::SHA1
end
canonicalize(xml, options = {}) click to toggle source

Accepts a document and optionally :path => xpath, :c14n_mode => c14n_mode

# File lib/samlr/tools.rb, line 34
def self.canonicalize(xml, options = {})
  options  = { :c14n_mode => C14N }.merge(options)
  document = Nokogiri::XML(xml) { |c| c.strict.noblanks }

  if path = options[:path]
    node = document.at(path, NS_MAP)
  else
    node = document
  end

  node.canonicalize(options[:c14n_mode], options[:namespaces])
end
decode(string) click to toggle source

CGI unescapes, Base64 decodes and inflates a string

# File lib/samlr/tools.rb, line 61
def self.decode(string)
  unescaped = CGI.unescape(string)
  decoded   = Base64.decode64(unescaped)
  inflater  = Zlib::Inflate.new(-Zlib::MAX_WBITS)
  inflated  = inflater.inflate(decoded)

  inflater.finish
  inflater.close

  inflated
end
encode(string) click to toggle source

Deflates, Base64 encodes and CGI escapes a string

# File lib/samlr/tools.rb, line 53
def self.encode(string)
  deflated = Zlib::Deflate.deflate(string, 9)[2..-5]
  encoded  = Base64.encode64(deflated)
  escaped  = CGI.escape(encoded)
  escaped
end
inflate(data) click to toggle source
# File lib/samlr/tools.rb, line 137
def self.inflate(data)
  inflater  = Zlib::Inflate.new(-Zlib::MAX_WBITS)
  decoded = inflater.inflate(data)
  inflater.finish
  inflater.close
  decoded
rescue Zlib::BufError, Zlib::DataError
  nil
end
parse(data, compressed: false) click to toggle source

Tries to parse the SAML request, returns nil if no data passed. First, it assumes it to be Base64 encoded. If this fails, it subsequently attempts to parse the raw input as select IdP's send that rather than a Base64 encoded value

# File lib/samlr/tools.rb, line 113
def self.parse(data, compressed: false)
  return unless data
  decoded = Base64.decode64(data)
  decoded = self.inflate(decoded) if compressed
  return unless decoded
  begin
    doc = Nokogiri::XML(decoded) { |config| config.strict }
  rescue Nokogiri::XML::SyntaxError => e
    begin
      doc = Nokogiri::XML(data) { |config| config.strict }
    rescue
      raise Samlr::FormatError.new(e.message)
    end
  end

  begin
    Samlr::Tools.validate!(:document => doc)
  rescue Samlr::SamlrError => e
    Samlr.logger.warn("Accepting non schema conforming response: #{e.message}, #{e.details}")
    raise e unless Samlr.validation_mode == :log
  end
  doc
end
uuid() click to toggle source

Generate an xs:NCName conforming UUID

# File lib/samlr/tools.rb, line 48
def self.uuid
  "samlr-#{UUIDTools::UUID.timestamp_create}"
end
validate(options = {}) click to toggle source

Validate a SAML request or response against an XSD. Supply either :path or :document in the options and a :schema (defaults to SAML validation)

# File lib/samlr/tools.rb, line 79
def self.validate(options = {})
  document = options[:document] || File.read(options[:path])
  schema   = options.fetch(:schema, SAML_SCHEMA)
  bang     = options.fetch(:bang, false)

  if document.is_a?(Nokogiri::XML::Document)
    xml = document
  else
    xml = Nokogiri::XML(document) { |c| c.strict }
  end

  # All bundled schemas are using relative schemaLocation. This means we'll have to
  # change working directory to find them during validation.
  Dir.chdir(Samlr.schema_location) do
    if schema.is_a?(Nokogiri::XML::Schema)
      xsd = schema
    else
      xsd = Nokogiri::XML::Schema(File.read(schema))
    end

    result = xsd.validate(xml)

    if bang && result.length != 0
      raise Samlr::FormatError.new("Schema validation failed", "XSD validation errors: #{result.join(", ")}")
    else
      result.length == 0
    end
  end
end
validate!(options = {}) click to toggle source
# File lib/samlr/tools.rb, line 73
def self.validate!(options = {})
  validate(options.merge(:bang => true))
end