Commit 6b5e200
Changed files (9)
lib/saml/kit/serializable.rb
@@ -1,28 +1,47 @@
module Saml
module Kit
module Serializable
+ # Base 64 decodes the value.
+ #
+ # @param value [String] the string to base 64 decode.
def decode(value)
Base64.decode64(value)
end
+ # Base 64 encodes the value.
+ #
+ # @param value [String] the string to base 64 encode.
def encode(value)
Base64.strict_encode64(value)
end
+ # Inflates the value using zlib decompression.
+ #
+ # @param value [String] the value to inflate.
def inflate(value)
inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
inflater.inflate(value)
end
- # drop header and checksum as per spec.
+ # Deflate the value and drop the header and checksum as per the SAML spec.
+ # https://en.wikipedia.org/wiki/SAML_2.0#HTTP_Redirect_Binding
+ #
+ # @param value [String] the value to deflate.
+ # @param level [Integer] the level of compression.
def deflate(value, level: Zlib::BEST_COMPRESSION)
Zlib::Deflate.deflate(value, level)[2..-5]
end
+ # URL unescape a value
+ #
+ # @param value [String] the value to unescape.
def unescape(value)
CGI.unescape(value)
end
+ # URL escape a value
+ #
+ # @param value [String] the value to escape.
def escape(value)
CGI.escape(value)
end
lib/saml/kit/service_provider_metadata.rb
@@ -5,24 +5,31 @@ module Saml
super("SPSSODescriptor", xml)
end
+ # Returns each of the AssertionConsumerService bindings.
def assertion_consumer_services
services('AssertionConsumerService')
end
+ # Returns the AssertionConsumerService for the specified binding.
+ #
+ # @params binding [Symbol] can be either `:http_post` or `:http_redirect`
def assertion_consumer_service_for(binding:)
service_for(binding: binding, type: 'AssertionConsumerService')
end
+ # Returns true when the metadata demands that Assertions must be signed.
def want_assertions_signed
attribute = document.find_by("/md:EntityDescriptor/md:#{name}").attribute("WantAssertionsSigned")
return true if attribute.nil?
attribute.text.downcase == "true"
end
+ # @!visibility private
def self.builder_class
Saml::Kit::Builders::ServiceProviderMetadata
end
+ # @deprecated Use 'Saml::Kit::Builders::ServiceProviderMetadata'.
Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::ServiceProviderMetadata::Builder', 'Saml::Kit::Builders::ServiceProviderMetadata')
end
end
lib/saml/kit/signature.rb
@@ -5,17 +5,20 @@ module Saml
@xml_hash = xml_hash
end
+ # Returns the embedded X509 Certificate
def certificate
value = to_h.fetch('KeyInfo', {}).fetch('X509Data', {}).fetch('X509Certificate', nil)
return if value.nil?
Saml::Kit::Certificate.new(value, use: :signing)
end
+ # Returns true when the fingerprint of the certificate matches one of the certificates registered in the metadata.
def trusted?(metadata)
return false if metadata.nil?
metadata.matches?(certificate.fingerprint, use: :signing)
end
+ # Returns the XML Hash.
def to_h
@xml_hash
end
lib/saml/kit/signatures.rb
@@ -1,5 +1,6 @@
module Saml
module Kit
+ # @!visibility private
class Signatures # :nodoc:
# @!visibility private
attr_reader :configuration
@@ -10,6 +11,7 @@ module Saml
@key_pair = configuration.key_pairs(use: :signing).last
end
+ # @!visibility private
def sign_with(key_pair)
@key_pair = key_pair
end
lib/saml/kit/templatable.rb
@@ -1,6 +1,9 @@
module Saml
module Kit
module Templatable
+ # Can be used to disable embeding a signature.
+ # By default a signature will be embedded if a signing
+ # certificate is available via the configuration.
attr_accessor :embed_signature
# @deprecated Use {#embed_signature=} instead of this method.
@@ -9,11 +12,12 @@ module Saml
self.embed_signature = value
end
+ # Returns the generated XML document with an XML Digital Signature and XML Encryption.
def to_xml(xml: ::Builder::XmlMarkup.new)
signatures.complete(render(self, xml: xml))
end
- # @api private
+ # @!visibility private
def signature_for(reference_id:, xml:)
return unless sign?
render(signatures.build(reference_id), xml: xml)
@@ -26,17 +30,17 @@ module Saml
signatures.sign_with(key_pair)
end
- # Returns true if an embedded signature is requested and ad least one signing certificate is available via the configuration.
+ # Returns true if an embedded signature is requested and at least one signing certificate is available via the configuration.
def sign?
embed_signature.nil? ? configuration.sign? : embed_signature && configuration.sign?
end
- # @api private
+ # @!visibility private
def signatures
@signatures ||= Saml::Kit::Signatures.new(configuration: configuration)
end
- # @api private
+ # @!visibility private
def encryption_for(xml:)
if encrypt?
temp = ::Builder::XmlMarkup.new
@@ -49,12 +53,12 @@ module Saml
end
end
- # @api private
+ # @!visibility private
def encrypt?
encrypt && encryption_certificate
end
- # @api private
+ # @!visibility private
def render(model, options)
Saml::Kit::Template.new(model).to_xml(options)
end
lib/saml/kit/template.rb
@@ -7,6 +7,9 @@ module Saml
@target = target
end
+ # Returns the compiled template as a [String].
+ #
+ # @param options [Hash] The options hash to pass to the template engine.
def to_xml(options)
template.render(target, options)
end
lib/saml/kit/trustable.rb
@@ -9,6 +9,7 @@ module Saml
validate :must_be_trusted
end
+ # Returns true when the document has an embedded XML Signature or has been verified externally.
def signed?
signature_manually_verified || signature.present?
end
@@ -19,6 +20,7 @@ module Saml
xml_hash ? Signature.new(xml_hash) : nil
end
+ # Returns true when documents is signed and the signing certificate belongs to a known service entity.
def trusted?
return true if signature_manually_verified
return false unless signed?
lib/saml/kit/xml.rb
@@ -10,8 +10,6 @@ module Saml
"samlp": Namespaces::PROTOCOL,
}.freeze
- attr_reader :raw_xml, :document
-
validate :validate_signatures
validate :validate_certificates
@@ -20,27 +18,31 @@ module Saml
@document = Nokogiri::XML(raw_xml)
end
- def x509_certificates
- xpath = "//ds:KeyInfo/ds:X509Data/ds:X509Certificate"
- document.search(xpath, Xmldsig::NAMESPACES).map do |item|
- Certificate.to_x509(item.text)
- end
- end
-
+ # Returns the first XML node found by searching the document with the provided XPath.
+ #
+ # @param xpath [String] the XPath to use to search the document
def find_by(xpath)
document.at_xpath(xpath, NAMESPACES)
end
+ # Returns all XML nodes found by searching the document with the provided XPath.
+ #
+ # @param xpath [String] the XPath to use to search the document
def find_all(xpath)
document.search(xpath, NAMESPACES)
end
+ # Return the XML document as a [String].
+ #
+ # @param pretty [Boolean] return the XML string in a human readable format if true.
def to_xml(pretty: true)
pretty ? document.to_xml(indent: 2) : raw_xml
end
private
+ attr_reader :raw_xml, :document
+
def validate_signatures
invalid_signatures.flat_map(&:errors).uniq.each do |error|
errors.add(error, "is invalid")
@@ -69,6 +71,13 @@ module Saml
end
end
end
+
+ def x509_certificates
+ xpath = "//ds:KeyInfo/ds:X509Data/ds:X509Certificate"
+ document.search(xpath, Xmldsig::NAMESPACES).map do |item|
+ Certificate.to_x509(item.text)
+ end
+ end
end
end
end
lib/saml/kit/xml_decryption.rb
@@ -1,12 +1,16 @@
module Saml
module Kit
class XmlDecryption
+ # The list of private keys to use to attempt to decrypt the document.
attr_reader :private_keys
def initialize(configuration: Saml::Kit.configuration)
@private_keys = configuration.private_keys(use: :encryption)
end
+ # Decrypts an EncryptedData section of an XML document.
+ #
+ # @param data [Hash] the XML document converted to a [Hash] using Hash.from_xml.
def decrypt(data)
encrypted_data = data['EncryptedData']
symmetric_key = symmetric_key_from(encrypted_data)